Saturday 27 July 2013

Debugging PowerShell scripts

This week I've been working on a couple of PowerShell scripts to automate a few tasks around deployment of an application and because I didn't know any better, I returned to the good bad old technique of writing out the variable values to see what the problem was. It turns out that it is possible to debug PowerShell scripts without using the ISE, which was crashing for me every time I tried to run it.

Enter the Set-PSBreakpoint cmdlet:


It's worth noting that you can set multiple breakpoints with the same command and as you can probably imagine they can be added to commands, variables or actions, for more details see this:
Get-help Set-PSBreakpoint -examples
In order to remove the all breakpoints, just use:
Get-PSBreakpoint | ForEach-Object {Remove-PSBreakpoint $_}
Or delete a single one
Remove-PSBreakpoint -id 0
Hope this helps, if it doesn't use Powershell ISE, don't waste your time adding write-host or echo statements to output values like I did this week.

Monday 22 July 2013

FetchXml linked entity limit in MS Dynamics CRM 2011

I discovered this today, it's only possible to have 9 linked entities on a fetchXml query. I suspect that this is due to laziness, as the AliasedValue only seems to go to 9.

I might be wrong and there could be a genuine reason, but ....

Wednesday 17 July 2013

List all checked-out files in TFS


If you, like me have used various servers workspaces to develop on, then this command might come useful to see which files you have checked out where.

(needs to be run from Visual Studio’s console)
tf status <yoursourceroot> /user:<youruser> /recursive /format:detailed
Run this to get the same ouput for all users:
tf status <yoursourceroot> /user:* /recursive /format:detailed

Friday 12 July 2013

Hosting a RESTful JSON WCF Service from a console app or a windows service.

I've been working for a while on an application, too long to explain what it actually does, but the bottom line is that it requires, or at least it could benefit from having, a RESTful WCF service hosted on both http and https endpoints.

I toyed with the idea of doing the application in Python as it uses some Python code but I decided to stick with what I knew as I wanted to finish it quickly. At any rate, here is the code:

This is simply shown as an example of how it could be done, if you follow it, your end point will be listening on http://<hostname>/store/ and can be invoked by simply navigating to it like this:

http://<hostname>/store?page=url 

In order for the application to listen on https you will need to have a valid certificate on your certificate store. The subject name should match the hostname of the machine running this application and then this should work:

https://<hostname>/store?page=url 

First the interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfJsonRestService
{
    [ServiceContract]
    public interface IStore
    {
        [OperationContract]
        bool Store(string item);
    }

}
Then the class implementing the interface:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Configuration;

namespace WcfJsonRestService
{
   
    public class Store : IStore
    {
        [WebInvoke(Method = "GET",
                    ResponseFormat = WebMessageFormat.Json,
                    UriTemplate = "store?page={item}")]
        public bool Store(string item)
        {
            //do stuff here

            return true;
        }

    }
}
And finally a Console application that hosts the service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WcfJsonRestService
{
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                using (ServiceHost host = new ServiceHost(typeof(RESTful)))
                {

                    AddServiceEndPoint(host, "https://{0}/store", true, "change me");
                    AddServiceEndPoint(host, "http://{0}/store", false);

                    host.Open();

                    Console.WriteLine("Service host running......");
                    Console.WriteLine("Press Any key at any time to exit...");

                    Console.Read();

                    host.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                Console.Read();
            }


        }

        private static void AddServiceEndPoint(ServiceHost host, string url, bool useSSLTLS, string certSubjectName="")
        {
            string addressHttp = String.Format(url,
                System.Net.Dns.GetHostEntry("").HostName);


            WebHttpBinding binding;

            if (useSSLTLS)
            {

                binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                binding.HostNameComparisonMode = HostNameComparisonMode.WeakWildcard;
                binding.CrossDomainScriptAccessEnabled = true;
            }
            else
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.None);
                binding.CrossDomainScriptAccessEnabled = true;
            }

            // You must create an array of URI objects to have a base address.
            Uri uri = new Uri(addressHttp);
            Uri[] baseAddresses = new Uri[] { uri };

            WebHttpBehavior behaviour = new WebHttpBehavior();
            // Add an endpoint to the service. Insert the thumbprint of an X.509 
            // certificate found on your computer. 
            host.AddServiceEndpoint(typeof(IRESTful), binding, uri).EndpointBehaviors.Add(behaviour);

            if (useSSLTLS)
            {
                host.Credentials.ServiceCertificate.SetCertificate(
                    StoreLocation.LocalMachine,
                    StoreName.My,
                    X509FindType.FindBySubjectName,
                    certSubjectName);
            }
        }
    }
}

Alternatively, the WCF service can be hosted by a Windows service. Code behind for windows service here:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using StoreAndConvert.WCFService;
using System.Security.Cryptography.X509Certificates;
using System.Configuration;


namespace StoreAndConvert.WindowsService
{
    public partial class Store : ServiceBase
    {

        string certSubjectName = string.Empty;

        ServiceHost host;

        public Store()
        {
            InitializeComponent();
        }


        protected override void OnStart(string[] args)
        {
            try
            {
                //Debugger.Launch();
                certSubjectName = ConfigurationManager.AppSettings["CertificateSubjectName"];

                host = new ServiceHost(typeof(StoreUrls));

                AddServiceEndPoint(host, "https://{0}/storeurl", true, certSubjectName);
                AddServiceEndPoint(host, "http://{0}/storeurl", false);

                host.Open();

                Trace.WriteLine("Service host running......");
                Trace.WriteLine("Listening on");

                foreach (ServiceEndpoint sep in host.Description.Endpoints)
                {
                    Trace.WriteLine(string.Format("endpoint: {0} - BindingType: {1}",
                        sep.Address, sep.Binding.Name));
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }

        }

        protected override void OnStop()
        {
            try
            {
                if (host != null)
                {
                    host.Close();
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex);
            }
        }

        private void AddServiceEndPoint(ServiceHost host, string url, bool useSSLTLS, string certSubjectName = "")
        {
            string addressHttp = String.Format(url,
                System.Net.Dns.GetHostEntry("").HostName);

            WebHttpBinding binding;

            if (useSSLTLS)
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                binding.HostNameComparisonMode = HostNameComparisonMode.WeakWildcard;
                binding.CrossDomainScriptAccessEnabled = true;
            }
            else
            {
                binding = new WebHttpBinding(WebHttpSecurityMode.None);
                binding.CrossDomainScriptAccessEnabled = true;
            }

            // You must create an array of URI objects to have a base address.
            Uri uri = new Uri(addressHttp);
            Uri[] baseAddresses = new Uri[] { uri };

            WebHttpBehavior behaviour = new WebHttpBehavior();
            // Add an endpoint to the service. Insert the thumbprint of an X.509 
            // certificate found on your computer. 
            host.AddServiceEndpoint(typeof(IStoreUrls), binding, uri).EndpointBehaviors.Add(behaviour);

            if (useSSLTLS)
            {
                host.Credentials.ServiceCertificate.SetCertificate(
                    StoreLocation.LocalMachine,
                    StoreName.My,
                    X509FindType.FindBySubjectName,
                    certSubjectName);
            }
        }
    }
}

Wednesday 10 July 2013

Run Wix installer using elevated permissions

In my last post I talked about setting a certificate binding for an IIS website from a Wix installer, which required elevated permissions in order for the operation to work.

The solution involved checking that the user was running using elevated permissions, which was simple enough but it turns out there is a far neater solution to achieve this:

 <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" InstallPrivileges="elevated" />

Sunday 7 July 2013

Assign Certificate (Set HTTPS Binding certificate) to IIS website from Wix Installer

I'm working on this project where we have a secure website and I was tasked with creating an installer for it. After quite a few searches and not coming up with any results I went down the Custom Action route.

Not shown here is how to install the website for which we are modifying the binding.

This is very simple, it just uses IIS server manager to set the binding for the certificate, note that since this operation requires elevation of permissions, there is a check to ensure that the user is running with elevated permissions, if this is not the case then an the NotElevated custom action will be triggered, and error messaged displayed and the installation will be rolled back.

This is the Custom Action code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Web.Administration;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Security.Principal;

namespace Installer.CustomActions
{
    public class CustomActions
    {
        const string protocol = "https";
        const string bindingPattern = "*:{0}:";


        [CustomAction]
        public static ActionResult UpdateBinding(Session session)
        { 
            ActionResult result = ActionResult.Failure;
            session.Log("Start UpdateBinding.");
   if (CheckRunAsAdministrator())
   {
    bool outcome=       UpdateBinding("Portal", protocol, string.Format(bindingPattern, session["SSLPORT"]), session["CERT"], session);
     if(outcome){result = ActionResult.Success;}
                            session.Log("End UpdateBinding.");
                            return result;
   }
   else
   {
       session.Log("Not running with elevated permissions.STOP");
              session.DoAction("NotElevated");
   }
        }

        private static bool UpdateBinding(string sitename, string protocol, string port, string certSubject, Session session)
        {
            bool result=false; 
            session.Log(string.Format("Binding info (Port) {0}.", port));
            session.Log(string.Format("Certificate Subject {0}.", certSubject));

            using (ServerManager serverManager = new ServerManager())
            {
                Site site = serverManager.Sites.Where(x => x.Name == sitename).SingleOrDefault();

                X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);

                var certificate = store.Certificates.OfType<X509Certificate2>().Where(x => x.Subject == certSubject).FirstOrDefault();

                if (certificate != null)
                {
                    session.Log(string.Format("Certificate - Friendly Name: {0}. Thumbprint {1}", certificate.FriendlyName, certificate.Thumbprint));

                    site.Bindings[0].CertificateHash = certificate.GetCertHash();
                    site.Bindings[0].CertificateStoreName = store.Name;
                    site.Bindings[0].BindingInformation = port;

                    serverManager.CommitChanges();
                    result=true;
                }

                session.Log(string.Format("Could not find a certificate with Subject Name:{0}.", certSubject));

                store.Close();

            }
            return result;    
        }

        /// <summary>
        /// Check that process is being run as an administrator
        /// </summary>
        /// <returns></returns>
        private static bool CheckRunAsAdministrator()
        {
            var identity = WindowsIdentity.GetCurrent();
            var principal = new WindowsPrincipal(identity);
            return principal.IsInRole(WindowsBuiltInRole.Administrator);
        }
    }
}
and here is the Wix markup that uses the above custom action :
<Product ....>
<!--All the rest of the stuff-->

    <Binary Id="CA" SourceFile="$(var.Installer.CustomActions.TargetDir)Installer.CustomActions.CA.dll"/>

    <CustomAction Id="UpdateBinding" BinaryKey="CA" DllEntry="UpdateBinding" Execute="immediate" Return="check" />

    <CustomAction Id="NotElevated" Error="Ensure that the Installer is Run with elevated permissions (i.e. Run as Administrator)" />

    <InstallExecuteSequence>
      <Custom Action="UpdateBinding" After="InstallFinalize">NOT Installed</Custom>
    </InstallExecuteSequence>
</Product>

Tuesday 2 July 2013

Assert.AreEqual() failing for strings that are equal

Today I almost lost it while doing the simplest of unit tests.

In essence, we had a plugin that would fire on an entity being updated and would set the name of a custom entity to a particular string retrieved from a remote service. The thing was that even though the strings were the same, Assert.AreEqual() was failing.

After many attempts with various StringComparison options, using Trim in a fit of desperation I created a method to check each character in the actual string against the expected string and lo and behold they were different.

The actual string, coming from CRM was using character 160, which is a non-breaking space while the C# code was using character 32, which is a simple space.

The solution was to replace the character 160 with character 32, now the unit tests pass.

Code:

const string FirstName="A Random Name";

[TestMethod]
public void CheckRuleName()
{
    Entity entity = Service.Retrieve("H2H_rule", RuleId, new Microsoft.Xrm.Sdk.Query.ColumnSet("H2H_name"));
    entity = UpdateEntity(entity, RuleId);
 
    string result = entity.Attributes["H2H_name"].ToString().Replace((char)160,(char)32);
 
    Assert.AreEqual(FirstName, result);
}