NullifyNetwork

The blog and home page of Simon Soanes
Skip to content
[ Log On ]

Archive - Historical Articles

You are viewing records from 08/07/2008 19:40:08 to 07/12/2009 16:37:23. I'll be adding support for selecting a date range in future.

This is a moan mostly, but has anyone else noticed that the developer story for silverlight development is totally utterly rubbish?

I had hoped that Microsoft would fix it for Silverlight 3 so that developers had a first class UI editor rather than having to go use a trial copy of Expression Blend with the worst user interface I have ever come across (I have MSDN but there's only ever an old version of it available and the current version appears to always be the CTP/Beta), or some third party app just to put down some buttons (with the 'run an app locally' addition and loads of other great features why has no time been spent on a decent design-time experience?).

I don't want to write XAML, I wouldn't mind adding controls using some C# code but I really hate XML, it's neither intuitive nor functional (can't easily call/make functions to draw X number of controls but must declare them individually).  I might be able to do Silverlight using 100% C# but it still comes down to the point of why is there no decent designer!???

I know this is supposed to be fixed in Visual Studio 2010 but it's really annoying.

Update: Now it's released I've had a chance to play with Expression Blend 3 and it's quite a bit better than the previous incarnations, and controls are now visible in a seperate section, so I think they are let off there as they've fixed the UI problems quite a bit; but the developer story for Silverlight is still terrible.

Permalink 

My ASP.NET server/composite controls were not getting events from child controls, but only on the real pages, they worked fine on test ones.  It turned out that I had a master page enabled but didn't have an ID set for it, so it was auto generating it.

Just add this to your master pages code file:-

protected override void OnInit(EventArgs e) 
{ 
	base.OnInit(e); 
	this.ID = "SomeName"; 
}

And child controls will start working as expected.  I don't often do web development so this stumped me for quite a while...

Permalink 

This is a story of how to handle a customer who could have ended up sending something back and getting expensive.

So: I've been interested in getting an Aeron chair or Freedom Task Chair or similar for a while now and only been put off by the price.  Eventually Amazon being the amazing pool of data it is made a suggestion of a similar one called a Cobham mesh back chair, which looked like it would fit the bill but only cost £270.

I obviously googled for more information, and found that whilst there weren't any reviews there was a place called chairoffice.co.uk selling the exact same Cobham mesh back chair for almost a hundred pounds less than the other suppliers out there (around £150 with VAT and delivery, or £119 ex VAT).  A quick background check to see if they were legitimate showed they were based really close to the actual manufacturer - Teknikoffice and happened to have their site hosted on the same server which explains the price difference a bit!!  I ordered after a particularly bad day at work in a sort of shopping to make myself feel good mood.

I was really impressed when it arrived the next day after ordering (very speedy dispatch), and I got right down to assembly after work - which was remarkably simple compared to a few other chairs.  Right after assembling it though I sat down in it and tried to adjust the headrest - and noticed that the back part had a hairline fracture, which when the headrest was extended was completely sheered through in shipping!!!!  I was gutted as the chair was really comfortable and I had that sick feeling of looking forward to a painful argument with a customer service rep.

I immediately sent a photo to the manufacturer and chairoffice customer service asking if they could supply me with the part that had broken and Mark in teknikoffice's customer service department replied almost immediately (appreciate this is around 10PM at night) and shipped me the entire back of one of the chairs as a replacement without any questions (which I got and fitted yesterday evening).

This is such good customer service it has made me want to talk about them.  Sometimes a faulty product can actually make you happier with the company if they deal with it correctly - and this is one of those cases; I don't think I have EVER encountered that good a customer service from any other company, so I would fully recommend if you're interested in a mesh back chair like an Aeron but a lot cheaper you get one from chairoffice.co.uk.

Permalink 

I even put OpenID on my own site in the latest iteration of its software as it is slowly growing in momentum, Scott Hanselman has details of how to implement OpenID support here.

This works particularly well with sites like Yahoo, where you can just put in the URL and be logged in near-instantly.

Permalink 

Very quick snippet that I just made use of to allow users to remotely change passwords over the web.  Just leave the domainname and username black to change the current users password.  This works great from ASP.NET and requires no special permissions, unlike some solutions that use LDAP or impersonation.

[DllImport("netapi32.dll", CharSet = CharSet.Unicode, 
   CallingConvention = CallingConvention.StdCall, SetLastError = true)]

static extern uint NetUserChangePassword(string domainname, string username, string oldpassword, string newpassword);
Permalink 

Just a quick example:-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Reflection;
using System.IO;

namespace RestrictedAppDomainTest
{
    class Program
    {
        /// <summary>
        /// Example program demonstrating AppDomains with restrictive security
        /// Note that it requires the existance of C:\windows\win.ini to actually demonstrate it, but you 
        /// can modify this to any other file.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //we want to define a level of access for this appdomain to have
            PolicyLevel levelOfAccess = PolicyLevel.CreateAppDomainLevel();

            //now we grant the permissions to do something specific:-
            PermissionSet executeOnly = new PermissionSet(PermissionState.None);

            //bare minimum to run at all
            executeOnly.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

            //if you wanted to grant the rights to read files then you need to grant FileIO:-
            //uncommenting this means that the appdomain created can complete its task rather than erroring 
            //as intended:-
            //  executeOnly.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, "C:\\Windows\\"));

            PolicyStatement executeOnlyStatement = new PolicyStatement(executeOnly);
            UnionCodeGroup applicablePolicy = new UnionCodeGroup(new AllMembershipCondition(), 
		executeOnlyStatement);
            //and tie the code group we defined to the level of access
            levelOfAccess.RootCodeGroup = applicablePolicy;
            
            //now we could add rights for things we trust, so they can assert that they
	     //want to perform operations that the appdomain normally can't:-
            /*
             * This is commented out because it grants the running assembly those rights, but this is an 
             * example of how to do it:-
             * 
             * After using this, you can use a MessageBox or form from inside the plugin.
             * 
             * (Change to a using multiple assemblies to restrict each one seperately)
             *
            
            //this example results in the UI being available
            PermissionSet displayUI = new PermissionSet(PermissionState.None);
            displayUI.AddPermission(new UIPermission(PermissionState.Unrestricted));
            //wrap the permission set in a statement
            PolicyStatement displayUIPolicyStatement = new PolicyStatement(displayUI);
            //And only grant this permission when the assembly has been strong named
            AssemblyName exampleAssembly = Assembly.GetExecutingAssembly().GetName();
            UnionCodeGroup selectPolicy = new UnionCodeGroup(new StrongNameMembershipCondition(
                        new StrongNamePublicKeyBlob(exampleAssembly.GetPublicKey()
                    ), exampleAssembly.Name, exampleAssembly.Version
                ), displayUIPolicyStatement);
            
            //and finally we add this new policy to the previous one so there's a chain
            applicablePolicy.AddChild(selectPolicy);
            
             */

            //create the appdomain with our basic permission set!
            AppDomain domain = AppDomain.CreateDomain("RestrictedDomain", null, 
		  AppDomain.CurrentDomain.SetupInformation); //do not supply the level permission using this
// constructor, you can't change it later using//SetAppDomainPolicy
            
            //define the total security policy here
            domain.SetAppDomainPolicy(levelOfAccess);

            //now we load our example class from below, for this example we are using the currently executing 
            //assemblies name this can be replaced with another filename, or full assembly name to load a 
            //particular one from the GAC for example
            string thisAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
            IPlugin examplePlugin = (IPlugin)domain.CreateInstanceAndUnwrap(thisAssemblyName, 
		"RestrictedAppDomainTest.ExampleClass");

            //this starts running a thread and doing other things that are potentially dodgy.
            examplePlugin.DoSomething();
            //(note that if this didn't choose to start a thread it would still run in a seperate thread anyway)

            //no (security) exception here, just to show we are unaffected by the appdomain's restrictions!
            string dummyData = File.ReadAllText("C:\\windows\\win.ini");

            Console.WriteLine("An app domain is now started and running in the background.");
            Console.WriteLine();
            Console.WriteLine("Press any key to terminate and unload the restricted appdomain!");                    
            Console.ReadKey();

            //This results in the termination of the thread the AppDomain//started, and unloading of
            //any resources (including locked files or assemblies).
            AppDomain.Unload(domain);
            
            Console.WriteLine("Domain has been unloaded.  Press any key to end.");
            Console.ReadKey();
        }
    }

    /// <summary>
    /// Defines an example plugin
    /// </summary>
    public interface IPlugin
    {
        void DoSomething();
    }

    /// <summary>
    /// An example (malicious) plugin
    /// </summary>
    public class ExampleClass : MarshalByRefObject, IPlugin
    {
        /// <summary>
        /// This is called from the unrestricted AppDomain
        /// </summary>
        public void DoSomething()
        {
            voidDelegate startDoingThings = new voidDelegate(longNaughtyOperation);
            //run the method on a different thread to demonstrate we can  	     //even start threads here
            startDoingThings.BeginInvoke(null, null);
        }

        private delegate void voidDelegate();

        private void longNaughtyOperation()
        {
            while (true)
            {
                try
                {
                    //this is denied so will fail
                    string dummyData = File.ReadAllText("C:\\windows\\win.ini");
                    //this won't ever happen hopefully :)
                    Console.WriteLine("Restricted AppDomain completed its work!!!");
                    //stop looping if the above works!
                    break;
                }
                catch (SecurityException)
                {
                    Console.WriteLine("Security exception as expected, trying  again so you can terminate this thread... :)");
                }
                Thread.Sleep(1000);
            }
        }
    }
}
Permalink 

I am not much of a Linux fan and mostly run Windows even for servers, but while that is true I appreciate both a wide ecosystem of operating systems incase something bad happens to Windows or a particular component, love the vastly lower cost of purchase and like both the freedom and opportunity to change things if you need to. I have one Linux server right now for example, and with Mono it can even host the same sites as the Windows servers.

My choice for Linux has been Debian since about 2001, when I settled on it after moving away from Mandrake and Redhat and their terrible RPM dependency chains. (I did try SUSE in the middle and found that to be extremely polished and usable but the reality is that I have Windows for polished.)

Anyway, I am glad to see that Debian finally has a graphical installer and the ability to run live from the CD/DVD!  You can get it from here for 32bit x86 CPU's:-

http://cdimage.debian.org/debian-cd/5.0.0/i386/

And from here for 64bit x86 CPU's:-

http://cdimage.debian.org/debian-cd/5.0.0/amd64/

The folders with BT in the name contain the bittorrent distribution files. 

Permalink 

TribesNext have released a patch (and a full free download!) to work around the shutting down of the login servers for Tribes 2 in late 2008, so it's now finally possible to play again.

The patch also fixes the sierra update errors that were intentionally created and means that new accounts no longer require a CD key (Vivendi gave the game away for free to promote a sequel but stopped giving out keys and shut down the servers even for those that originally bought the game back in 2001 when it was released).

Now I'm just hoping that someone can update the engine to the latest version of the Torque engine from Garage Games (this is the game that gave me the incentive to buy a license for it originally).

Permalink 

I needed to get the unmanaged container name of an X509Certificate2 in order to be able to get access to the equivalent private key for it on Windows Mobile (I also wanted to be able to use the same routine on Windows CE and Win32 so this works as-is on all platforms).

No decent commentary but hopefully it will help someone else who is stuck without the container name or something.


#region Structures
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CRYPT_KEY_PROV_INFO
{
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pwszContainerName;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string pwszProvName;
      public uint dwProvType;
      public uint dwFlags;
      public uint cProvParam;
      public IntPtr rgProvParam;
      public uint dwKeySpec;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CRYPTOAPI_BLOB
{
    public uint cbData;
    public IntPtr pbData;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CERT_DSS_PARAMETERS
{
    public CRYPTOAPI_BLOB p;
    public CRYPTOAPI_BLOB q;
    public CRYPTOAPI_BLOB g;
}
#endregion

[DllImport("crypt32.dll")]
public static extern bool CertGetCertificateContextProperty(IntPtr pCertContext, 
	uint dwPropId, IntPtr pvData, ref uint pcbData);

public CRYPT_KEY_PROV_INFO GetCertificateContextProperty(X509Certificate2 certificate)
{
     //call once to get the size, call again to get the data
     try
     {
         IntPtr certhandle = certificate.Handle;
         uint pcbData = 0;
         if (CertGetCertificateContextProperty(certhandle, 2, IntPtr.Zero, ref pcbData))
         {
             IntPtr memoryChunk = Marshal.AllocHGlobal((int)pcbData);
             try
             {
       		    if (CertGetCertificateContextProperty(certhandle, 2, memoryChunk, 
			ref pcbData))
                  {
                    CRYPT_KEY_PROV_INFO context = 
				(CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(memoryChunk, 
				typeof(CRYPT_KEY_PROV_INFO));
                     return context;
                  }
                  else
                  {
                      throw new Exception("Failed to fetch the Certificate Context 
Property, possibly due to the certificate being modified during the call.  Please try again!");
                  }
              }
              finally
              {
                  Marshal.FreeHGlobal(memoryChunk);
              }
          }
      }
      finally
      {
          //dispose of certhandle or not??
      }
      throw new Exception("Failed to fetch the Certificate Context Property");
}

(Incidentally the extra structures are handy for doing other operations with the certificate so I left them there - the blob particularly is used in a lot of places in the CryptoAPI)

Permalink 

(This post is getting updated as I use things and need to be able to find things easier)

Generate a new private key (but no x509 public cert yet):-

openssl genrsa -aes128 -out selfsignedprivkey.key 2048

And then generate the public certificate to go with it:-

openssl req -new -x509 -days 7300 -key selfsignedprivkey.key -out selfsignedpubkey.cer

To convert from a pkcs12 key (sometimes called a .pfx file) to an OpenSSL key without a password to protect it:-

openssl pkcs12 -in file.p12 -out file.pem -nodes

(Handy for various open source servers that don't take pcks12 keys or use CryptoAPI on Windows, including stunnel, openvpn, hmailserver 5, webmin/apache, etc - omit -nodes if the system you are exporting for supports passwords on the private key.)

The converse when using OpenSSL to make a pkcs12 bundle is:-

openssl pkcs12 -export -inkey privkey.pem -in pubcert.cer -out combined.p12

And make a new certificate request:-

openssl req -new -out request.txt

And then there's sign certificate requests with the above key (assuming you edited your openssl.cfg file to point to certificates and keys that could have been generated above):-

openssl ca -in request.txt -out response.cer

The response is effectively the public certificate for the key file saved during the request generation, just use the two as-is.  To use the cert and key you just generated in windows, use the pkcs12 -export line above.

Note that if you want to sign request that include details of the subjectAltName (for multiple hostnames or e-mail addresses for example) then you need to make sure that "copy_extensions = copy" is not commented out in the openssl.cfg file.  If you do this though watch out, someone can make themselves a sub-CA with their certificate then if you don't check every request carefully.

And finally make a CRL for it (you should edit the openssl.cfg file to set the location of this on certificates prior to signing any requests as it's included in the certificate - use crlDistributionPoints = URI:http://www.yourdomain.net/ca-crl.crl in addition to nsCaRevocationUrl  = http://www.yourdomain.net/ca-crl.crl as otherwise windows won't think there is one there):-

openssl ca -gencrl -out mycrl.crl

Other things to note when using OpenSSL CA mode: index.txt needs to be completely empty, the serial needs to have 01 and a carriage return line feed ( echo 01>serial ) and you need to create crlnumber as being 01.

Permalink 

The multiple minute delay where Visual Studio freezes doing this is easy to fix and related to checking for necessary components on the target; it's actually down to the Platform Verification Task defined in the MS Build targets for the compact framework, but in .NETCF 3.5 you can just skip it by adding the following build rule (or you can set this up to apply to your entire system using the environment variables screen):-

set SkipPlatformVerification=true

Pre .NETCF 3.5 I think you need to manually add this as a constraint on the target, just google for SkipPlatformVerification for an example of how to do that.

Permalink 

Microsoft just announced Windows Azure at the PDC this year, a cloud hosting system!  This is cool because it has the ability to host both ASP.NET web sites (or WCF web services) and back-end compute components (UI less programs that run based on a queue system) with zero up-front infrastructure requirements and the ability to increase the number of systems serving it just by changing a single number!

The scaling is suddenly Microsoft’s problem as long as you write it to be queue based and to use the SDS storage system for your data, which is really very cool.

Combining this with the release of Silverlight 2.0 and you suddenly have the ability to make very-very rich applications available to end clients without the upfront investments you’d normally have to provide.

The only thing I have noticed that is a bit strange is that Microsoft have chosen to only allow developers to use the URL myapp.cloudapp.net rather than their own domain (maybe that is just the PDC account being signed up to use cloudapp.net!).

I tried it offline using the SDK (available at the link above) and the development experience is really quite good but I'm still waiting for access to the real thing to give it a proper test.

Permalink 

So I've been occupied as the schools came back over September and am just starting to catch back up, amongst the things to have snuck up on me are Silverlight and a lovely new report designer!

The Silverlight 2.0 SDK tools are here. They are badly missing any mouse interactivity on the design surface, which makes it seem like this is a big ploy to sell lots of copies of expression Blend to developers since that DOES support mouse control and addition of components.  Since Blend has one of the worst UI's I've ever come across (looks pretty but works like drawing in tar) I think I'll wait till this is fixed up or just edit the XML.  The ability to run C# code in silverlight and debug it properly almost lets Microsoft off but not quite.

However saying that Microsoft have released a new report designer for SQL 2008 Reporting Services.  It uses a slightly different schema for its RDL files than the 2005 report viewer but is an amazing standalone tool.  Hopefully after some work to support the format better (we work fine with 2005) work will be able to point clients at this so they can make their own reports more easily!

Permalink 

I just noticed that the PXE bootloader supplied in the syslinux package (in the gpxe folder) is now based on gPXE (link is to a video google have recorded where they show it off) which supports booting the host operating system over a really wide variety of TCP based protocols!  gPXE is based on etherboot (or is it renamed now as they merged the projects after forking them).

Previously stage 2 PXE bootloaders (like pxelinux) only loaded things using TFTP, but with the addition of a full TCP/IP stack you can now boot your entire computer over HTTP from a standard webserver located anywhere on the planet.

You can even boot a full copy of Windows using iSCSI without a hardware host bus adapter!

You may wonder why this is relevant to someone that does .NET pretty much all the time like me, but I like to boot servers and workstations off the network for backup/restore purposes, rapidly build test systems in virtual PC and to run new .NET based operating systems that are starting to show up.

Edit: I have noticed a lot of people coming to this article so think it's worth elaborating on the whole PXE bootloader process quickly, basically you do the following:-

Configure your DHCP server (or download one, there's a freeware DHCP server for windows if you don't have a proper server, or you can always apt-get install dhcpd and edit the config file) to serve up your normal subnet plus the addition of a bootfile name called (lets assume you're using PXELinux with gPXE!) gpxelinux.0 (see the syslinux package above and extract this file) and the IP or hostname of your boot server.

Your boot server needs to run a TFTP server, in the root of the TFTP server you put a copy of gpxelinux.0 and a config file for it.  For the moment just boot a virtual machine or something and see if it shows the PXE bootloader on startup.

Once you have it loading and erroring about a lack of configuration file, it's time to make a config; for gpxelinux.0 this is just a text file, and there's tons of examples in the syslinux package - but you can just put this in there to get started:-

default local
prompt 1

label local
 MENU label ^Local Boot
 LOCALBOOT 0
 timeout 10

label dsl
 MENU LABEL Linu^x
 linux linux24.bin
 append init=/etc/init lang=us apm=power-off vga=791 ramdisk_size=100000 acpi=off quiet nomce noapic BOOT_IMAGE=knoppix
 initrd minirt24.gz

And stick a copy of linux24.bin and minirt24.gz from DSL or your favourite small linux system in your TFTP server.  I have had great success making those links - so http://yourserver/linux24.bin instead works wonderfully with gPXE.

Permalink 

(It's very rare that I post during work hours, but my excuse is I need to pass the links around between machines and this is the easiest way)

Microsoft have released Visual Studio 2008 Service Pack 1!  Hopefully there's many good fixes in it, I haven't had time to beta test it so am really looking forward to it.

Finally it's also now possible to install SQL Server 2008...

The release is available along with .NET 3.5 SP1 in the form of an ISO image or the web installer!  The release notes (contains details of both updates) are also available.

Permalink  2 Comments 

Just a quick post with the ports for SQL Server as I keep muddling up the browser and the db engine:-

The database engine is port 1433
 
SQL Browser is on port 1434
 
And finally analysis services is on port 2383

Permalink 

Okay so to make up for not having posted a single codeblog entry in several months I have a mammoth one here that has taken over 6 hours to figure out and write.

The following is a class that implements near-full support for the WMI interface to the MS DNS Server on Windows Server 2003 (also should work on Windows Server 2008 and if you install an add-on you can probably use it under Server 2000 but I have only briefly tried server 2008).  I wrote this out of frustration when I googled for an easy way to create a DNS record programmatically in Microsoft DNS Server and there was a distinct absence of actually useful information (there were just a couple of poor examples and no really complete solutions).  You can use this to allow you to modify a DNS zone in C# (dynamic DNS hosting under Windows anyone?) or for running your webhosting businesses automation from ASP.NET.  Note that I haven't tested deleting!

This class requires both a reference to and a using statement for System.Management from .NET 2.0.  I included the entire thing as if it were a single file for the ease of demonstration - you probably want to remove the DNSServer class and its helper classes (which are included within it) and put them into a seperate file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;

namespace DNSManager
{
    #region Example usage code
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Connecting to the DNS Server...");
            DNSServer d = new DNSServer("vex.nullify.net"); //my internal DNS Server, change to yours.
                                                            //You will need to be able to get to it using WMI.
            Console.WriteLine("Connected to the DNS Server");

            Console.Write("Creating a new zone as a test...");
            try
            {
                d.CreateNewZone("testzone.uk.nullify.net.", DNSServer.NewZoneType.Primary);
                Console.WriteLine("OK");
            }
            catch (Exception)
            {
                Console.WriteLine("Failed to create a new zone, it probably exists.");
            }

            Console.Write("Creating a DNS record as a test...");
            try
            {
                d.CreateDNSRecord("testzone.uk.nullify.net.", "test1.testzone.uk.nullify.net. IN CNAME xerxes.nullify.net.");
                Console.WriteLine("OK");
            }
            catch (Exception)
            {
                Console.WriteLine("Failed to create a new resource record, it probably exists");
            }

            Console.WriteLine("Getting a list of domains:");
            foreach (DNSServer.DNSDomain domain in d.GetListOfDomains())
            {
                Console.WriteLine("\t"+domain.Name+" ("+domain.ZoneType+")");
                //and a list of all the records in the domain:-
                foreach (DNSServer.DNSRecord record in d.GetRecordsForDomain(domain.Name))
                {
                    Console.WriteLine("\t\t"+record);
                    //any domains we are primary for we could go and edit the record now!
                }
            }

            Console.WriteLine("Fetching existing named entry (can be really slow, read the warning):-");
            DNSServer.DNSRecord[] records = d.GetExistingDNSRecords("test1.testzone.uk.nullify.net.");
            foreach (DNSServer.DNSRecord record in records)
            {
                Console.WriteLine("\t\t" + record);
                record.Target = "shodan.nullify.net.";
                record.SaveChanges();
            }
        }
    }
    #endregion

    #region Real code

    /// <summary>
    /// A Microsoft DNS Server class that abstracts out calls to WMI for MS DNS Server
    /// </summary>
    /// <remarks>
    /// WMI Documentation: 
    /// http://msdn.microsoft.com/en-us/library/ms682123(VS.85).aspx
    /// System.Management Documentation: 
    /// http://msdn.microsoft.com/en-us/library/system.management.managementobjectcollection%28VS.71%29.aspx
    /// </remarks>
    /// <c>(c) 2008 Simon Soanes, All Rights Reserved.  No warranties express or implied.
    /// DO NOT redistribute this source code publically without a link to the origin and this copyright
    /// notice fully intact, also please send any modifications back to me at simon@nullifynetwork.com
    /// Including in your software is fine although attribution would be nice.</c>
    public class DNSServer
    {

        #region Supporting classes
        /// <summary>
        /// Different types of DNS zone in MS DNS Server
        /// </summary>
        public enum ZoneType
        {
            DSIntegrated,
            Primary,
            Secondary
        }

        /// <summary>
        /// Different types of DNS zone in MS DNS Server
        /// </summary>
        /// <remarks>For creation of new zones the list is different</remarks>
        public enum NewZoneType
        {
            Primary,
            Secondary,
            /// <remarks>Server 2003+ only</remarks>
            Stub,
            /// <remarks>Server 2003+ only</remarks>
            Forwarder
        }

        /// <summary>
        /// A zone in MS DNS Server
        /// </summary>
        public class DNSDomain
        {        
            /// <summary>
            /// Create a DNS zone
            /// </summary>
            /// <param name="name">The name of the DNS zone</param>
            /// <param name="wmiObject">The object that represents it in MS DNS server</param>
            /// <param name="server">The DNS Server it is to be managed by</param>
            public DNSDomain(string name, ManagementBaseObject wmiObject, DNSServer server)
            {
                _name = name;
                _wmiObject = wmiObject;
                _server = server;
            }

            private DNSServer _server = null;

            private string _name = "";

            /// <summary>
            /// The name of the DNS zone
            /// </summary>
            public string Name
            {
                get { return _name; }
                set { _name = value; }
            }

            /// <summary>
            /// The zone type
            /// </summary>
            public ZoneType ZoneType
            {
                get
                {
                    //_wmiObject["ZoneType"].ToString()
                    return (ZoneType)Convert.ToInt32(_wmiObject["ZoneType"]);
                }
            }

            /// <summary>
            /// Is this a reverse DNS zone?
            /// </summary>
            public bool ReverseZone
            {
                get
                {
                    if (_wmiObject["Reverse"].ToString() == "1")
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            private ManagementBaseObject _wmiObject = null;

            /// <summary>
            /// Get a list of all objects at the base of this zone
            /// </summary>
            /// <returns>A list of <see cref="DNSRecord"/></returns>
            public DNSRecord[] GetAllRecords()
            {
                return _server.GetRecordsForDomain(_name);
            }

            /// <summary>
            /// Create a new DNS host record
            /// </summary>
            /// <param name="record">The record to create</param>
            public void CreateDNSRecord(DNSRecord record)
            {
                _server.CreateDNSRecord(_name, record.ToString());
            }

            public override string ToString()
            {
                return _name;
            }
        }

        /// <summary>
        /// An entry in a zone
        /// </summary>
        public class DNSRecord
        {
            /// <summary>
            /// Create an class wrapping a DNS record
            /// Defaults to 1 hour TTL
            /// </summary>
            /// <param name="domain"></param>
            /// <param name="recordType"></param>
            /// <param name="target"></param>
            public DNSRecord(string domain, DNSRecordType recordType, string target) : 
                this(domain, recordType, target, new TimeSpan(1, 0, 0))
            {
            }

            /// <summary>
            /// Create an class wrapping a DNS record
            /// </summary>
            /// <param name="domain"></param>
            /// <param name="recordType"></param>
            /// <param name="target"></param>
            /// <param name="ttl"></param>
            public DNSRecord(string domain, DNSRecordType recordType, string target, TimeSpan ttl)
            {
                _host = domain;
                _ttl = ttl;
                _target = target;
                _recordType = recordType;
            }

            /// <summary>
            /// Create an class wrapping a DNS record
            /// </summary>
            /// <param name="wmiObject"></param>
            public DNSRecord(ManagementObject wmiObject)
            {
                _wmiObject = wmiObject;
                _host = wmiObject["OwnerName"].ToString();
                _target = wmiObject["RecordData"].ToString();
                string[] recordParts = wmiObject["TextRepresentation"].ToString().Split(' ', '\t');
                if (recordParts.Length > 2)
                {
                    //the third offset is the location in the textual version of the data where the record type is.
                    //counting from zero that is location 2 in the array.
                    _recordType = new DNSRecordType(recordParts[2]); 
                }
                _ttl = new TimeSpan(0, 0, Convert.ToInt32(wmiObject["TTL"]));
            }

            private ManagementObject _wmiObject = null;

            private string _target = "";

            /// <summary>
            /// The value of the target is what is written to DNS as the value of a record
            /// </summary>
            /// <remarks>For MX records include the priority as a number with a space or tab between it and the actual target</remarks>
            public string Target
            {
                get { return _target; }
                set { _target = value; }
            }

            /// <summary>
            /// Save the changes to the resource record
            /// </summary>
            public void SaveChanges()
            {
                //We can call modify and if we have the method available it will work as the sub-class may have it!!
                //Some types DO NOT implement it or implement it differently

                ManagementBaseObject parameters = _wmiObject.GetMethodParameters("Modify");
                bool supported = false;

                //This is a cludge based on the various types that are implemented by MS as they didn't stick to a simple value

                //To add more, please refer to 
                if (RecordType.TextRepresentation == "A")
                {
                    parameters["IPAddress"] = _target;
                    parameters["TTL"] = _ttl.TotalSeconds;
                    supported = true;
                }

                if (RecordType.TextRepresentation == "AAAA")
                {
                    parameters["IPv6Address"] = _target;
                    parameters["TTL"] = _ttl.TotalSeconds;
                    supported = true;
                }

                if (RecordType.TextRepresentation == "CNAME")
                {
                    parameters["PrimaryName"] = _target;
                    parameters["TTL"] = _ttl.TotalSeconds;
                    supported = true;
                }

                if (RecordType.TextRepresentation == "TXT")
                {
                    parameters["DescriptiveText"] = _target;
                    parameters["TTL"] = _ttl.TotalSeconds;
                    supported = true;
                }

                if (RecordType.TextRepresentation == "MX")
                {
                    string[] components = _target.Trim().Split(' ', '\t');
                    if (components.Length > 1)
                    {
                        parameters["Preference"] = Convert.ToUInt16(components[0]); //the preference is a UINT16 in MS DNS Server
                        parameters["MailExchange"] = components[1]; //the actual host name
                        //NOT SUPPORTED BY MX ACCORDING TO THE DOCUMENTATION!? parameters["TTL"] = _ttl;
                        supported = true;
                    }
                }

                Exception temporaryException = null;
                try
                {
                    //This supports improving this classes implementation of this method and adding 
                    ManagementBaseObject lastDitchParameters = OnSaveChanges(parameters);
                    if (lastDitchParameters != null)
                    {
                        parameters = lastDitchParameters;
                        supported = true;
                    }
                }
                catch (Exception ex) //catch all as we do not know what someone will modify OnSaveChanges() to throw or cause
                {
                    if (!supported) //if we support the data type already then we don't care about exceptions as at worst case
                        throw;
                    else
                        temporaryException = ex;
                }

                if (supported)
                {
                    try
                    {
                        _wmiObject = (ManagementObject)_wmiObject.InvokeMethod("Modify", parameters, null);
                    }
                    catch (Exception ex)
                    {
                        if (temporaryException != null)
                        {
                            throw new ApplicationException("There were two exceptions, the primary failure"+
                                " was an exception that is encapsulated in this message however additionaly "+
                                "a virtual method that was optional to functionality also threw an exception "+
                                "but this was withheld till after the operation failed. Please examine the"+
                                " InnerException property for copy of the virtual methods exception.  The "+
                                "virtual methods exception message was: " + temporaryException.Message+".  "+
                                "The primary exceptions message (a "+ex.GetType().FullName.ToString()+") "+
                                "was: "+ex.Message, temporaryException);
                        }
                        else
                        {
                            throw;
                        }
                    }
                    
                    if (temporaryException != null)
                    {
                        throw new ApplicationException("A virtual method that was optional to functionality "+
                            "threw an exception but this was withheld till after the operation completed "+
                            "successfully, please examine the InnerException property for a full copy of this "+
                            "exception.  The message was: " + temporaryException.Message, temporaryException);
                    }
                }
                else
                {
                    throw new NotSupportedException("The data type you attmpted to use ("+
                        RecordType.TextRepresentation+") was not supported, please implement support for"+
                    "it by overriding the method OnSaveChanges() and returning an array of filled WMI parameters "+
                    "or by updating this implementation.");
                }
            }

            /// <summary>
            /// Method to override to add additional methods to the DNS save changes support
            /// </summary>
            /// <param name="parametersIn">An array of parameters (not yet filled in if it's an 
            /// unknown type, potentially partially filled for known types)</param>
            /// <returns>An array of filled in parameters, or null if the parameters are unknown</returns>
            public virtual ManagementBaseObject OnSaveChanges(ManagementBaseObject parametersIn)
            {
                return null;
            }

            /// <summary>
            /// Delete a record from DNS
            /// </summary>
            public void Delete()
            {
                _wmiObject.Delete();
                //well that was easy...
            }

            private TimeSpan _ttl = new TimeSpan(0, 1, 0);

            /// <summary>
            /// The time that the resolvers should cache this record for
            /// </summary>
            public TimeSpan Ttl
            {
                get { return _ttl; }
                set { _ttl = value; }
            }

            private DNSRecordType _recordType = null;

            /// <summary>
            /// The record type
            /// </summary>
            public DNSRecordType RecordType
            {
                get { return _recordType; }
            }
            private string _host = "";

            /// <summary>
            /// The location in the DNS system for this record
            /// </summary>
            public string DomainHost
            {
                get { return _host; }
                set { _host = value; }
            }

            public override string ToString()
            {
                return _host + " " + _recordType.ToString() + " " + _target;
            }
        }

        /// <summary>
        /// The type of record in MS DNS Server
        /// </summary>
        public class DNSRecordType
        {
            /// <summary>
            /// Create a new DNS record type
            /// </summary>
            /// <param name="textRepresentation">The type to create</param>
            public DNSRecordType(string textRepresentation)
            {
                _textRepresentation = textRepresentation;
            }
            private string _textRepresentation = "";

            /// <summary>
            /// The text representation of the record type
            /// </summary>
            public string TextRepresentation
            {
                get
                {
                    return _textRepresentation.ToUpper();
                }
            }

            private string _recordMode = "IN";

            /// <summary>
            /// The mode of the record, usually IN but could oneday be something else like OUT
            /// </summary>
            public string RecordMode
            {
                get
                {
                    return _recordMode;
                }
                set
                {
                    _recordMode = value;
                }
            }

            public override string ToString()
            {
                return _recordMode+" "+_textRepresentation;
            }

            #region Some Defaults!
            /// <summary>
            /// An alias
            /// </summary>
            public static DNSRecordType CNAME 
            {
                get    { return new DNSRecordType("CNAME"); }
            }

            /// <summary>
            /// An IPv4 address
            /// </summary>
            public static DNSRecordType A
            {
                get { return new DNSRecordType("A"); }
            }
            
            /// <summary>
            /// A reverse host address inside yoursubnet.in-addr.arpa
            /// </summary>
            public static DNSRecordType PTR
            {
                get { return new DNSRecordType("PTR"); }
            }
    
            /// <summary>
            /// An MX record (mail exchange)
            /// </summary>
            public static DNSRecordType MX
            {
                get { return new DNSRecordType("MX"); }
            }

            /// <summary>
            /// An IPv6 host address
            /// </summary>
            public static DNSRecordType AAAA
            {
                get { return new DNSRecordType("AAAA"); }
            }

            /// <summary>
            /// A text record
            /// </summary>
            public static DNSRecordType TXT
            {
                get { return new DNSRecordType("TXT"); }
            }

            /// <summary>
            /// A nameserver record (domain delegation)
            /// </summary>
            public static DNSRecordType NS
            {
                get { return new DNSRecordType("NS"); }
            }

            /// <summary>
            /// An SOA record (start of authority)
            /// </summary>
            public static DNSRecordType SOA
            {
                get { return new DNSRecordType("SOA"); }
            }

            #endregion
        }
        
#endregion

        /// <summary>
        /// Create a new DNS Server connection
        /// </summary>
        /// <param name="server">The hostname, IP or FQDN of a DNS server you have access to with the current credentials</param>
        public DNSServer(string server)
        {
            ConnectionOptions co = new ConnectionOptions();
            _scope = new ManagementScope(String.Format(@"\\{0}\Root\MicrosoftDNS", server), co);
            _scope.Connect();  //no disconnect method appears to exist so we do not need to manage the 
                               //persistence of this connection and tidy up
        }

        /// <summary>
        /// Create a new DNS Server connection
        /// </summary>
        /// <param name="server">The hostname, IP or FQDN of a DNS server you have access to with the current credentials</param>
        /// <param name="username">The username to connect with</param>
        /// <param name="password">The users password</param>
        public DNSServer(string server, string username, string password)
        {
            ConnectionOptions co = new ConnectionOptions();
            co.Username = username;
            co.Password = password;
            co.Impersonation = ImpersonationLevel.Impersonate;
            _scope = new ManagementScope(String.Format(@"\\{0}\Root\MicrosoftDNS", server), co);
            _scope.Connect();
        }

        private string _server = "";

        /// <summary>
        /// The server this connection applies to
        /// </summary>
        public string Server
        {
            get { return _server; }
        }

        private ManagementScope _scope = null;

        /// <summary>
        /// Return a list of domains managed by this instance of MS DNS Server
        /// </summary>
        /// <returns></returns>
        public DNSDomain[] GetListOfDomains()
        {
            ManagementClass mc = new ManagementClass(_scope, new ManagementPath("MicrosoftDNS_Zone"), null);
            mc.Get();
            ManagementObjectCollection collection =  mc.GetInstances();

            List<DNSDomain> domains = new List<DNSDomain>();
            foreach (ManagementObject p in collection)
            {
                domains.Add(new DNSDomain(p["ContainerName"].ToString(), p, this));
            }

            return domains.ToArray();
        }

        /// <summary>
        /// Return a list of records for a domain, note that it may include records
        /// that are stubs to other domains inside the zone and does not automatically
        /// recurse.
        /// </summary>
        /// <param name="domain">The domain to connect to</param>
        /// <returns></returns>
        public DNSRecord[] GetRecordsForDomain(string domain)
        {
            string query = String.Format("SELECT * FROM MicrosoftDNS_ResourceRecord WHERE DomainName='{0}'", domain);
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(_scope, new ObjectQuery(query));
            
            ManagementObjectCollection collection = searcher.Get();

            List<DNSRecord> records = new List<DNSRecord>();
            foreach (ManagementObject p in collection)
            {
                records.Add(new DNSRecord(p));
            }

            return records.ToArray();
        }

        /// <summary>
        /// Create a new DNS host record
        /// </summary>
        /// <param name="zone"></param>
        /// <param name="bindStyleHostEntry"></param>
        /// <returns></returns>
        public void CreateDNSRecord(string zone, string bindStyleHostEntry)
        {
            try
            {
                ManagementObject mc = new ManagementClass(_scope, new ManagementPath("MicrosoftDNS_ResourceRecord"), null);
                mc.Get();
                ManagementBaseObject parameters = mc.GetMethodParameters("CreateInstanceFromTextRepresentation");
                parameters["ContainerName"] = zone;
                parameters["DnsServerName"] = _server;
                parameters["TextRepresentation"] = bindStyleHostEntry;
                ManagementBaseObject createdEntry = mc.InvokeMethod("CreateInstanceFromTextRepresentation", parameters, null);
                //return createdEntry; (no reason unless you changed your mind and wanted to delete it?!)
            }
            catch (ManagementException) //the details on this exception appear useless.
            {
                throw new ApplicationException("Unable to create the record " + bindStyleHostEntry + ", please check"+
                    " the format and that it does not already exist.");
            }
        }

        /// <summary>
        /// Create a new DNS host record
        /// </summary>
        /// <param name="zone"></param>
        /// <param name="record"></param>
        public void CreateDNSRecord(string zone, DNSRecord record)
        {
            CreateDNSRecord(zone, record.ToString());
        }

        /// <summary>
        /// Fetch DNS records for a particular name
        /// WARNING: This method has performance issues, iterate over the results of getting all the records for a domain instead.
        /// </summary>
        /// <remarks>Returns a collection as one hostname/entry can have multiple records but it can take longer
        /// than getting all the records and scanning them!</remarks>
        /// <param name="hostName">The name to look up</param>
        /// <returns></returns>
        public DNSRecord[] GetExistingDNSRecords(string hostName)
        {
            string query = String.Format("SELECT * FROM MicrosoftDNS_ResourceRecord WHERE OwnerName='{0}'", hostName);
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(_scope, new ObjectQuery(query));

            ManagementObjectCollection collection = searcher.Get();
            List<DNSRecord> records = new List<DNSRecord>();
            foreach (ManagementObject p in collection)
            {
                records.Add(new DNSRecord(p));
            }

            return records.ToArray();
        }

        /// <summary>
        /// Create a new zone in MS DNS Server
        /// </summary>
        /// <param name="zoneName">The zone to create</param>
        /// <param name="zoneType">The type of zone to create</param>
        /// <returns>The domain</returns>
        public DNSDomain CreateNewZone(string zoneName, NewZoneType zoneType)
        {
            try
            {
                ManagementObject mc = new ManagementClass(_scope, new ManagementPath("MicrosoftDNS_Zone"), null);
                mc.Get();
                ManagementBaseObject parameters = mc.GetMethodParameters("CreateZone");

                /*
                [in]            string ZoneName,
                [in]            uint32 ZoneType,
                [in]            boolean DsIntegrated,   (will always be false for us, if you need AD integration you will need to change this.
                [in, optional]  string DataFileName,
                [in, optional]  string IpAddr[],
                [in, optional]  string AdminEmailName,
                */

                parameters["ZoneName"] = zoneName;
                parameters["ZoneType"] = (UInt32)zoneType;
                parameters["DsIntegrated"] = 0; //false
                ManagementBaseObject createdEntry = mc.InvokeMethod("CreateZone", parameters, null);
                DNSDomain d = new DNSDomain(zoneName, createdEntry, this);
                return d;
            }
            catch (ManagementException) //returns generic error when it already exists, I'm guessing this is a generic answer!
            {
                throw new ApplicationException("Unable to create the zone "+zoneName+", please check "+
                    "the format of the name and that it does not already exist.");
            }
        }

        public override string ToString()
        {
            return _server;
        }
    }
    #endregion
}
Permalink  8 Comments 

Here I am installing SQL Server 2008 and I notice that it's installing .NET Framework 3.5 SP1.

There's a total lack of announcements so I'm not sure, but given that it's bonded at the hip with Visual Studio 2008 SP1 I am expecting it to install that next (and saw it when extracting the ISO).  Only thing is it seems (updated this post after hitting this) you can't use the SP1 that is shipped with SQL server to update your normal copy of VS2k8, it only does the limited shell.

I have found what seems to be the sole blog entry over at blogs.msdn.com that has any mention of it: http://blogs.msdn.com/euanga/archive/2008/08/07/sql-server-2008-installation-confusion-vs-2008-sp1-and-netfx-3-5-sp1.aspx which seems to confirm it's the RTM version at least but it opens a can of worms by saying that you need VS 2008 SP1 and that it isn't available to install against the non-sql server bits.

Regardless of if it has been officially released, .NET 3.5 SP1 (but apparently not yet Visual Studio SP1 and all its goodness) is certainly out now anyway...

Permalink 

Well the subject says it all!

MSDN subscribers can now download SQL Server 2008 - I'm sure an SQL Express 2008 edition will come out soon.  Interestingly there is an extra edition available on MSDN - "Web Edition", this is pure speculation but I'm guessing this is standard edition without CALs (or SQL Express without the memory, cpu and filesize restrictions if you want to look at it that way) so you can use it on webservers.

I downloaded a copy of it this morning and will install it in a bit to try it out.  Hopefully the software at work will be fine with it (we tested on an old release candidate but haven't recently) but it'll be good to have the new data types (finally we get a seperate date and time type, and the spatial data types are awesome) and an improved management studio either way.

Another feature that will be good is the ability to dictate the resources a particular database (or user) can take up, preventing a particular website or application consuming all the resources on a server.

Permalink