Pages

Friday, September 25, 2015

Blog Reboot and Xamarin


How many blogs must there be where the author started blogging and then author just stopped posting. I never thought it happen to me. I enjoyed blogging, yet nearly 2 years ago I stopped. For me it was a new employer for whom I was hired on to work as an consultant, After being hired I was told the company was worried how its clients would react to my blog so I was to immediately stop blogging. 

No big deal I thought, I didn't want to rock the boat at a new job so I stopped blogging. Looking back now that should have sent alarm bells screaming in my head but it didn't at the time. 

Fast forward a year and I had left that job due to the stress of my employer blatantly misrepresenting itself, its bad practices and then expecting me to lie to our clients. It was a stressful time, not so much because of the clients but due to the corporate consulting culture of just placing a consults in positions that in no way matched their skills.  Just so they could charge the client those "expert consultant fees" for a just graduated novice being paid a fraction of what they charged the client.

Finding a Great Company

I left the consultant culture to work for a small company full of great people called Road Id. Its been busy but rewarding, many of our customers email Road ID to tell us how our products have really saved lives.

I have tons of blog post material I've created there the last 2 year, some highlights that come to mind. 
  • Migrate to Azure from Rackspace
  • Design and built a scaling back-end for the Road ID Mobile App
  • Built service layer and repository pattern around Entity Framework
  • Replaced internal winforms app with a Angular Single Page App

On top of that I've become a father and there is no words to describe how much I love my son nor how little sleep I got those first 3 months after he was born.  About the same time my son was born my old domain  "ChrisTowles.com" expired and someone snagged it. I tried purchasing it back but no luck. So i'm climbing the search engine ranks again with a new domain name. 

Blog's Future 

I wanted update this blog because right I have been working with a amazing technology stack that I really have high expectations of it and more importantly of what I can do with it. That technology is Xamarin and what the Xamarin team has built is amazing. 

While I'll follow this post up with more details on Xamarin the bottom line is you can build native Android and iOS apps in C# and use Visual Studio to do it. For me that means using the language and IDE I am most productive with and conformable using to make quality mobile apps. 

I expect many future blog posts on Xamarin Development to share my journey from Xamarin Novice to Expert.

Tuesday, February 5, 2013

Create a Pharos Uniprint Logon Script to resolve ADLDAPLogon.exe Short Falls

I got a few calls from users complaining about our Pharos Clients getting "The username and password could not be authenticated against any servers" error Message. Now while a lot of users where getting this error it was only a small percentage of the over all print jobs. After digging around and putting a call into Pharos Support I noticed that this wasn't just a recent start of errors but rather common. If you want to check for it you can search your alerts or table or use Pharos administrator to and a Custom filter on the alerts.

SELECT        TOP (100) PERCENT message, username, client, time
FROM            dbo.alerts
WHERE        (message LIKE '%The username and password could not be authenticated against any servers%')
ORDER BY time DESC

Cause


Turns out the ADLDAPLogon.exe basically just don't do much checking as to why a username failed. From what testing I did I turns out the following all return the same error.
  • If the username appended with any  SMTP Address of @domain.com to the username
  • If the username is incorrect in that no account in Pharos with that name exists.
  • if the username doesn't match AD
  • .... I'm sure there are others.

Solution 

As such I talking with the Pharos Support Rep they could write a Plug-in that called ADLDAPLogon.exe but that it would be a charged Service on their part. As such I wrote my own this morning and figured I'd share.

The following Script will do the following check and then check if the username and password are correct.

  • Confirm that the Username exists in the Pharos Database
  • That the PlugIn.UserName is not empty
  • That the PlugIn.Password is not empty
  • Remove any @domain.com from the username
This script does require that ADLDAPLogon.exe is in place and configured correctly.

Then Create the Script in under system. Go to your bank, and change the Logon event from useing ADLDAPLogon.exe to use the newly created Logon Script instead. Do a change control and you done.




Good luck and feel free to  use and modify this script to fit your needs. 


//  PlugIn Script: Logon - NKU ADLDAPLogon.exe
// 
//  Billing PlugIn to allow stripping the username of @domain.com before passing to adldaplogon.exe 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  Project:        Pharos 8.3
//  Design:         NKU
//  Date:           5-February-2013
//  Where:          Northern Kentucky University 
//  Who:            Chris Towles written from scratch
//  --------------------------------------------------------------
//  Modifications:
//    02-05-13 Chris Towles : Created to strip the username of @domain.com before passing to adldaplogon.exe 
//   
//
// Description:
//    This Logon Script allows a full email address to be used as the username. It strips the '@' and everything that follows then calls
//
// Requirements:
//    1. adldaplogon be installed and configured
//        C:\Program Files (x86)\Pharos\Bin>adldaplogon.exe --list
//
//  
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// import namespaces
import "DB";
import "Win32";
import "String";  
import "IO";

import "User";

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Constants (customizable)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

//#region Constants

//...string
new sScriptName          = "Logon - NKU ADLDAPLogon.exe";
new sLogPrefix           = "[" + sScriptName + "] -> ";

IO.PrintLine("Solution: " + sScriptName);



// While the path can be hard-coded here as a string, this tends to break in heterogeneous
// environments, e.g. mixed 32- and 64-bit servers.  If possible, install plug-ins relative
// to the Pharos installation folder and query the registry for the starting point:
new sPharosPath = Win32.RegQueryValue("SOFTWARE\\Pharos\\Installed Components", "Path");

// Win32.SearchPath will return the shortened path and file names.
// Be sure to specify the remainder of the path relative to the Pharos folder.
new sADLDAPLogonPath            = Win32.SearchPath(sPharosPath + "\\Bin\\adldaplogon.exe");

//new sADLDAPLogonPath = "C:\\Program Files (x86)\\Pharos\\Bin\\adldaplogon.exe";

new eErrorInvalidUserNamePassword      = "Invalid username or password";
new eErrorInvalidUserName              = "The given username doesn't exist. Please be sure to enter your AD username.";
new eErrorPlugInResultsNotValid        = "Results from Logon plug-in are not valid. Please contact the information desk.";
new eErrorUsernameIsEmpty              = "You must enter a Username.";
new eErrorPasswordIsEmpty              = "You must enter a password.";


// - Logon Plug-in timeout (milliseconds)
new iCmdTimeout = 30000;


//#region Variables
//...boolean
new bResult = false;
new bIsPharosAccountAvailable  = false;


//...integer
new iPos;
new iUserID;


//...string
new UserName = PlugIn.UserName;
new sResultsFile;
new sADLDAPLogonCommand = "";
new sADLDAPLogonResult = "";
new sADLDAPLogonError = "";
new sResult = "";


//---------------------------------------------------------------------------------------
// Functions code
//---------------------------------------------------------------------------------------

function IsAccountAvailable(name)
{
    try
    {
        IO.PrintLine(sLogPrefix + "Get user by logon id.");
        User.GetUserByLogon(name);
        return true;
    }
    catch
    {
        IO.PrintLine(sLogPrefix + "Failed to get user by logon id. Get user by card id.");
        try
        {
            User.GetUserByCardID(name);
            return true;
        }
        catch
        {
            IO.PrintLine(sLogPrefix + "Failed to get user by logon id or card id.");
            return false;

        }
    }

}


//---------------------------------------------------------------------------------------
// PlugIn code
//---------------------------------------------------------------------------------------

//---------------------------------------------------
// Default the script to fail. Set default error
// message to Invalid Username/Password. 
//---------------------------------------------------
PlugIn.Result = false;
PlugIn.Error = eErrorInvalidUserNamePassword;


//---------------------------------------------------------------------------------------
// Clean Up the Username and strip the domain name off of it
//---------------------------------------------------------------------------------------


if (String.IsEmpty(PlugIn.UserName) )
{
    IO.PrintLine(sLogPrefix + " User entered an empty username. Fail the logon.");
    PlugIn.Result = false;
    PlugIn.Error = eErrorUsernameIsEmpty;
}
else { //strip the @Domain.com from the account. 
    
    iPos = String.Find(UserName, "@");
    if (iPos != -1)
    {
        String.Left(UserName, iPos);
    }
    iPos = 0;

    
    
    bIsPharosAccountAvailable = IsAccountAvailable(UserName);

    if (bIsPharosAccountAvailable == false)
    {
        IO.PrintLine(sLogPrefix + "User Account doesn't exist.");
        PlugIn.Result = false;
        PlugIn.Error = eErrorInvalidUserName + " : " + UserName;
    }   
    else 
    {
    
        //From now on in the script user "UserName" instead of PlugIn.UserName

        //---------------------------------------------------
        // Check if the user must enter a password (if
        // enabled.
        //---------------------------------------------------
        if (String.IsEmpty(PlugIn.Password) )
        {
            IO.PrintLine(sLogPrefix + "User entered an empty password. Fail the logon.");
            PlugIn.Result = false;
            PlugIn.Error = eErrorPasswordIsEmpty;
        }
        else
        {
            //---------------------------------------------------
            //Verify the users Password
            //---------------------------------------------------

            //adldaplogon.exe out.txt user  
      
            sResultsFile = Win32.GetTempFileName();
            sADLDAPLogonCommand = sADLDAPLogonPath + " " +
                        sResultsFile + 
                        " user " +
                        UserName + " " +
                        PlugIn.Password;
        
            //Write to the Pharos Print Server log Note this would have the username and password
            //IO.PrintLine ( ">sADLDAPLogonCommand :: " + sADLDAPLogonCommand);
        
            Win32.ExecProcess(sADLDAPLogonCommand, iCmdTimeout);
        
            bResult = IO.LoadFile(sADLDAPLogonResult, sResultsFile);
        
            //Write to the Pharos Print Server log
            IO.PrintLine ( "> Logon bResult :: " + bResult);
            IO.PrintLine ( "> Logon sADLDAPLogonResult :: " + sADLDAPLogonResult);

            //Clean Up and delete the Temp Output file            
            IO.DeleteFile(sResultsFile);

            if (bResult)
            {
              //get first line from sADLDAPLogonResult
              sResult = sADLDAPLogonResult;
              iPos = String.Find(sADLDAPLogonResult, "\r\n");
              if (iPos != -1)
              {
                  String.Left(sResult, iPos);
                  String.UpperCase(sResult);
                  String.Delete(sADLDAPLogonResult, 0, String.Length(sResult));
                  String.TrimLeft(sADLDAPLogonResult);
              }

              if (sResult == "FAIL")
              {
                     //ADLDAPLogon Failed
                  iPos = String.Find(sADLDAPLogonResult, "\r\n");
                  sADLDAPLogonError = sADLDAPLogonResult;
                  if (iPos != -1)
                     {
                    String.Left(sADLDAPLogonError, iPos);
                  }

                     IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe returned a FAIL : " + sADLDAPLogonError);
                    
                     PlugIn.Result = false;
                     PlugIn.Error = eErrorInvalidUserNamePassword + " : " + UserName;
                     IO.PrintLine(sLogPrefix + "Returning the user this error : " + PlugIn.Error );
                 }    
              else 
                 {
                    if (sResult == "OK")
                 {
                        // Logon was successful.
                  IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe login was successful for user '" + UserName + "'");
                        PlugIn.Result = true;
                    }
                   else
                    {
               //...Unkown Error 
                        IO.PrintLine(sLogPrefix + "ADLDAPLogon.exe Result was not Valid");
                    PlugIn.Error = eErrorPlugInResultsNotValid;
                    PlugIn.Result = false;
                    }
                 }
              } //end of  if (bResult)
           } //end of password check
        }// Check if Pharos Account exists
    }//End of Username Check

Tuesday, January 8, 2013

Claims Based Authentication - Setting up Visual Studio 2012 on Localhost to use ADFS

So currently I'm  learning Windows Identity Foundation to do Claims Based Authentication. I'm working an a few posts on the subject and i'll be posting them soon. For now this one focuses on the my need to configure my Visual Studio environment to use my domain ADFS server as a STS. Now I know i can setup a local STS project for testing but I wanted to use our domain ADFS so that everything as close as possible to production configuration as I could for testing.

Turns out this isn’t hard to do but a few configuration steps that are easy to miss. Since I didn't find any guides or posts on how to do this I figured I’d share mine.

Overview

The end result will be a ADFS relaying service provider that will expect inbound connection from https://localhost and a Visual studio configuration set to use IIS, rather than IIS express, hosting the VS Project. What's really interesting is that since i configured this to use https://localhost any developer can use the the same relaying party for their testing rather than creating a Replaying Party Entry in ADFS for every developer machine. Also because the links will only work from the localhost you don’t need to worry about them deploying projects using that Replying Party entry because it won’t work.

Solution

There are two sides of this setup. First I’ll cover the settings I configured on the ADFS with a relaying relaying party and this those of the client machine running Visual studio.

ADFS Configuration

From the ADFS server navigate to the Relying Party Trusts and add a new Relaying Party Trust. We need to supply a FederationMetadata.xml file. I modified this one for our use. Create a text file and copy the contents to it and save it as “Localhost_FederationMetadata.xml”

Localhost_FederationMetadata.xml

<?xml version="1.0" encoding="utf-8"?>
<EntityDescriptor ID="_3d1176b1-236d-4675-8970-674b061daf17" 
                entityID="https://localhost/"
                xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
    <RoleDescriptor xsi:type="fed:ApplicationServiceType" 
                xmlns:fed="http://docs.oasis-open.org/wsfed/federation/200706"
                protocolSupportEnumeration="http://docs.oasis-open.org/wsfed/federation/200706"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <fed:TargetScopes><wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
            <wsa:Address>https://localhost/</wsa:Address></wsa:EndpointReference>
        </fed:TargetScopes>
        <fed:PassiveRequestorEndpoint>
            <wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
                <wsa:Address>https://localhost/</wsa:Address>
            </wsa:EndpointReference>
        </fed:PassiveRequestorEndpoint>
    </RoleDescriptor>
</EntityDescriptor>

With the file created Choose “Import data about the relying party from a file”.

clip_image002


Friday, October 12, 2012

Pharos Mobile Print: Print server Firewall

Pharos was nice enough to let us Demo Pharos MobilePrint and since there isn't much out there on Pharos Mobile Print I thought I'd post a few small things that might help some of you. I'm using Pharos MobilePrint version 1.2.

If your sending email to Pharos Mobile Print it has to verify your email address. From the documentation MobilePrint and Uniprint Integration located on the Pharos install media.
MobilePrint first checks if the user’s email address is in its internal cache (if the user has printed before their address will already be in the cache). If it is not found MobilePrint then checks in the Pharos Database.
If you are sure that the email account is correctly set on the user account. Either from Pharos Administrator or the DB.

SELECT  [user_id]      
,[active]      
,[id]      
,[email]  
FROM [pharos].[dbo].[users]  
where id like 'username1'

If its set and the service is still asking your accounts to register their email address instead it means the Pharos MobilePrint Server for what ever reason can't connect to the Pharos MobilePrint Auth Service. Check that the firewall port is open on the print server. Port 808. However if you have this issue refer to the Mobile Print Installation and Configuration Guide and check all the ports because its likely you have more than just this one firewall port issue.


The Pharos MobilePrint Log, which i set to “c:\PharosLogs” will display the following.

[2012/10/11 15:10:24 PDE8 T018 d MP_ConsignmentTracker] 84e2ba58-9023-561d-af42-3ee75a505f29 Indexing email from 'test1@test.nku.edu'
[2012/10/11 15:10:24 PDE8 T05F d MP_ConsignmentTracker] 84e2ba58-9023-561d-af42-3ee75a505f29 Starting Workflow for sender 'test1@test.nku.edu'
[2012/10/11 15:10:24 PDE8 T05F d MP_ConsignmentTracker] 84e2ba58-9023-561d-af42-3ee75a505f29 Sender email address not in cache
[2012/10/11 15:10:45 PDE8 T025 w MP_WorkflowService] An attempt was made to resume a Workflow Instances that has either completed or been deleted. This request will now be ignored.
[2012/10/11 15:10:45 PDE8 T026 e MP_WorkflowServiceExtension] An error occurred while attempting to process a response on the 'authentication' queue for message '465738ac-0a35-5c90-bfe2-b038a51f7dbd'.
[2012/10/11 15:10:45 PDE8 T026 e MP_WorkflowServiceExtension] Exception: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Type: CommunicationObjectFaultedException
StackTrace:   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelFactory.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelFactory.TypedServiceChannelFactory`1.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ChannelFactory.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ChannelFactory.System.IDisposable.Dispose()
   at MobilePrint.Service.Workflow.Extensions.AuthenticationManager.<.ctor>b__0(AuthenticateResponse response)
[2012/10/11 15:10:45 PDE8 T026 e MP_RestInterface] An unhandled error occurred while processing a REST command.
[2012/10/11 15:10:45 PDE8 T026 e MP_RestInterface] Exception: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Type: CommunicationObjectFaultedException
StackTrace:   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelFactory.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelFactory.TypedServiceChannelFactory`1.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ChannelFactory.OnClose(TimeSpan timeout)
   at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan timeout)
   at System.ServiceModel.ChannelFactory.System.IDisposable.Dispose()
   at MobilePrint.Service.Workflow.Extensions.AuthenticationManager.<.ctor>b__0(AuthenticateResponse response)
   at MobilePrint.Core.Workflow.Library.QueueManager.<>c__DisplayClass1`1.b__0(BaseQueueResponse o)
   at System.Reactive.AnonymousObserver`1.Next(T value)
   at System.Reactive.AbstractObserver`1.OnNext(T value)
   at System.Reactive.Subjects.Subject`1.OnNext(T value)
   at MobilePrint.Core.Workflow.Library.QueueManager.PushResponse(String queueName, BaseQueueResponse packet)
   at MobilePrint.Service.Workflow.RestResource.WebResource.PushQueue(String queueName, HttpRequestMessage request)
[2012/10/11 15:10:45 PDE8 T025 w MP_WorkflowService] An attempt was made to resume a Workflow Instances that has either completed or been deleted. This request will now be ignored.
[2012/10/11 15:10:45 PDE8 T026 e MP_WorkflowServiceExtension] An error occurred while attempting to process a response on the 'authentication' queue for message 'e71cfa3f-0d71-52b2-a538-9ab27ed45271'.
[2012/10/11 15:10:45 PDE8 T026 e MP_WorkflowServiceExtension] Exception: The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.
Type: CommunicationObjectFaultedException


Wednesday, September 19, 2012

Vmware ESXi 5.0 update 1 Sending Traffic out unused VMNIC because of Failback

We've had some troubled with our Equallogic SAN Performance which lead to us really looking at the iSCSI/Equallogic best practices. This post with a few others will address what we've learned and I wish I could say the Support call to Dell Equallogic Team has been really useful but expect for the exception of one tech named "Chris" that really shared some useful information on the Equallogic side of things we've been on our own for the last 3 weeks.

One of those problems I noticed and shared with the Equallogic team was that following Equallogic's Configuring and Installing the EqualLogic Multipathing Extension Module for VMware vSphere and PS Series SANs  guide to MPIO for iSCSI targets Traffic on the ESXi Host was coming out of the incorrect  vmnic.

The Environment:

  • ESXi Host 5.0.0 build 768111 fully patched as of 9-16-2012
  • Dell Equallogic PS6100XS with firmware 5.2.5
  • Using EqualLogic-ESX-Multipathing-Module v1.1.1

The Problem

If you follow the install guide for the EqualLogic-ESX-Multipathing-Module and use the setup.pl script for the configuring of a vSwitch or Distributed Switch  (vDS) you'll create two ISCSI vmk's and a storage heartbeat vmk.
vSwitch Setup for just iSCSI
On both of those iSCSI networks you configure Nic Teaming and Failover to only use one of Physical Adapters and mark the other unsed and of course alterternate which one is marked unused for the other ISCSI Network.


The Dell setup.pl script marks the failback to No on each iSCSI Network. So  following these best practices in my setup I would expect that vmk2 traffic could only come out vmnic2 as vmnic3 is set to be Unused. And vise versa vmk3 traffic would only come out vmnic3 as vmnic2 is marked as Unused for it.


However when I SSH to the ESXi host and run "esxtop" and hit "n" for network I see the following showing that vmk2 is infact using that what is suppose to be an "unused" vmnic3.  I pointed this out to Dell and they sais it was odd didn't have any answers yet they ever followed up with me on it. I got asked for vmware supports but not even asked to recreate it.



The Fix

After setting this up every way I could think of; trying it using both vSwitches and Distributed Switches, rebuilding my hosts from CD, trying different ESX hosts with different hardware, tried hosts on different network infrastructure. All with the same issue of it using the incorrect nic.

After all I used the script to create the setup to ensure it was correct and best practice. I create it by hand instead of the script and double checked every setting to ensure everything matched. Still no luck. So of course after it was already time to go home I checked ESXTOP one last time on a ESX host I wasn't finished configuring  and behold it was working correctly. Each vmk was bound to its correct vmnic.

vmk using the correct vmnic
What I hadn't done was set the failback to no yet. Everything else was done.


The Reason this Happened
After doing some reading in this epic post by Joshua Townsend that laid out the resent changes round VMware iSCSI Networking. In fact his quick fix at the time was to in fact turn on failback to no. Looking at the EqualLogic-ESX-Multipathing-Module v1.1.1 setup.pl you can see it following Joshua's quick fix and setting the failback option. I also found the same thing in version 1.1.0. The scripts comments even say that its doing so because of a Vmware bug. However VMware says that bug is now fixed (VMware KB 2008144) and instead looks like setting this option introduces a bug instead.


So if you used the Dell EqualLogic-ESX-Multipathing-Module setup script or followed the install guide you may want to check if you do in fact have this problem because Network throughput, Multipathing and Network Redundancy may not work as you expect.


Wednesday, August 22, 2012

Issue Installing FTP within IIS when you have the Firewall Enabled


So in Windows 2008 R2, there is a bug when installing FTP within IIS when you have the firewall enabled.

After you install the role service, the system automatically sets up the firewall rules needed and enabled them.  One of those inbound rules is “FTP Server (FTP Traffic-In)”.  Though this port should be open you see that if you enable firewall logging its dropping any traffic on this port.   The problem comes from the service “ftpsvc” didn’t get its service SID set correctly.  More on service SIDs can be found at http://sourcedaddy.com/windows-7/understanding-service-sids.html  and http://blogs.technet.com/b/askperf/archive/2008/02/03/ws2008-windows-service-hardening.aspx.

To view the current SID for ftpsvc run the following from a command problem.

sc qsidtype ftpsvc

                (Note: You can’t just use “sc” in PowerShell because “sc” is an alias for Set-Content.)

Which should give the following output.

[SC] QueryServiceConfig2 SUCCESS

SERVICE_NAME: ftpsvc
SERVICE_SID_TYPE:  UNRESTRICTED

This looks correct, but if you run the following command that sets the service sid to what it already is:

sc sidtype ftpsvc unrestricted
               
                Then restart ftpsvc with:

                                net stop ftpsvc
                                net start ftpsvc

The service now works, this has been a problem of over 2 years and a bug report exists at http://connect.microsoft.com/WindowsServerFeedback/feedback/details/524831/default-ftp-firewall-port-21-rule-is-broken-in-windows-2008-r2.  Thank you Transsient77 for the fix.