Friday, 27 February 2015

Multi threaded windows service

We recently had a requirement to host an internal windows service which would continuously process small, but relatively long (for example 15-90 seconds) running packets of work obtained from a queue.

 We also wanted a degree of parallelism for efficiency, the service being hosted on a relatively powerful multi-core windows server.

 Each packet of work would represent a business critical process - so we were keen to ensure that the design of the service was robust and that stopping the service for any reason would not cause worker threads to be terminated abruptly and in addition there was sufficient scope for error handling and compensation within the service.

 We did not have a requirement to cluster an individual service instance. It quickly became apparent that we needed to carefully consider how the windows Service-Control-Manager interacted with our service.

 What started off as a relatively trivial bit of code ended up being slightly more complex. I couldn't find very many good examples, so here's an idea of how the orchestrating service start and stop logic could be implemented, should it be useful.

  Note: This is an example and not production quality code. All comments welcomed.

 1. Starting
  • The first goal with this example is to ensure that the service OnStart() method clears quickly (it isn't blocked) 
  • That we start a "foreground thread" to manage the background threads. Foreground threads are less likely to be abruptly terminated by windows.
In our service example we can start by defining some low level constructs to help us manage the threads and provide a wait event to trigger stopping (this could equally have been implemented as a TPL cancellation token) This code goes at the root of the service class inheriting from ServiceBase

  private const int ThreadCount = 3;
  private readonly CountdownEvent _threadCounter = new CountdownEvent(1);
  private readonly ManualResetEvent _stopWork = new ManualResetEvent(false);

Next we define the overridden OnStart method implementation
  protected override void OnStart(string[] args)
  {
   Debug.WriteLine("OnStart() - Max background threads allowed ={0}" , ThreadCount);

   var thread = new Thread(OrchestrateBackgroundTasks) { Name = "Foreground", IsBackground = false };
   var pool = new Semaphore(ThreadCount, ThreadCount + 1);
            thread.Start(new WaitHandle[] { pool, _stopWork }); // pass argument object here

   Debug.WriteLine("OnStart() - complete");
  }

Note the method doesn't get blocked, the thread is defined as foreground (it's not background!) and set to work. The thread which entered the OnStart() method then exits.

 2. Stopping.
  • The second goal is to ensure that the OnStop() method has a way of signalling the orchestrating [foreground] thread that it must wind up without starting any more workers
  • The OnStop() method should also wait for any currently running background threads to complete gracefully.
  • It should also keep SCM (Service Control Manager) informed periodically
  • It shouldn't run forever, there should be a time out
  protected override void OnStop()
  {
   Debug.WriteLine("OnStop()");
   // signal stop
   _stopWork.Set();
   // wait on any still active threads, comment this out if waiting on the pool 
   // in OrchestrateBackgroundTasks() 
   // however doing that means that SCM could try and teardown the service instance
   // before it completes as it won't receive regular feedback
   WaitOnRunningTasksToComplete();
   Debug.WriteLine("OnStop() - complete");
  }


The OnStop Implementation signals the stop event then blocks waiting on running tasks to complete. Only when all background threads have completed gracefully or the time out reached will this method complete.

The OrchestrateBackgroundTasks method, which is run on our foreground thread, acquires two objects.

1. The "pool" represented here by a Semaphore
2. A reference to the stop event

As each task completes (but only to the maximum number of tasks allowed by the pool) a new task is scheduled. The task continuation is used to handle / log worker errors and signal that a new task can be enqueued.

Once the stop event is set/triggered the foreground thread and this procedure will exit.

  private void OrchestrateBackgroundTasks(object args)
  {
   // TODO: type and null checks required
   var handles = args as WaitHandle[];

   Debug.WriteLine("DoWork()");

   // wait on pool, or stop
   while (WaitHandle.WaitAny(handles) == 0)
   {
    // maintain pool
    var task = Task.Factory.StartNew(LongRunningBackgroundTask, _threadCounter.CurrentCount);

    //Increment the worked thread counter
    _threadCounter.AddCount();

    // once the background task is complete, handle any error and
    // signal the pool so a new worker task can be added (above)
    task.ContinueWith(t =>
    {
     // this code only runs when the background task completes
     if (t.IsFaulted)
     {
      // faulted 
      // log exception etc
     }

     Debug.WriteLine(string.Format("DoWork() - A background Task completed"));
     // release from pool and signal complete
     (handles[0] as Semaphore).Release();
     _threadCounter.Signal();
     
    });
   }

   // Indicate that the main thread is exiting.
   Debug.WriteLine("DoWork() signals main thread complete");
   _threadCounter.Signal();

   // Note: 
   // Could wait *here once stop has been signalled
   // rather than calling WaitOnRunningTasksToComplete() from OnStop()
   // ...by adding this line
   //_threadCounter.Wait();
   // However, within WaitOnRunningTasksToComplete() we can add code to
   // report back to the Service Control Manager (SCM) which in long
   // running scenarios helps windows understand the service is waiting and hasn't died.

   Debug.WriteLine("DoWork() - complete");
  }



The WaitOnRunningTasksToComplete method simply waits on the background thread counter or until time out, whichever happens sooner. Periodically it will signal back to the SCM via a ServiceBase method!
This helps with the end user experience.

Note: Thread sleep is used here, because we want the thread that entered OnStop() to be blocked.

  private void WaitOnRunningTasksToComplete()
  {
   Debug.WriteLine("Cleanup()");
   const int timeout = 5000; // 5 secs
   const int spinTotal = 5;
   var spinCounter = 0;

   while (_threadCounter.CurrentCount > 0 && spinCounter < spinTotal)
   {
    Debug.WriteLine("Cleanup() - blocking {0}ms", timeout);
    Thread.Sleep(timeout);// block thread
    if (_threadCounter.CurrentCount > 0)
    {
     Debug.WriteLine("Cleanup() - Work outstanding, ask SCM for {0}ms additional time", timeout);
     base.RequestAdditionalTime(timeout);
    }
    spinCounter++;
   }
   if (_threadCounter.CurrentCount > 0)
   {
    Debug.WriteLine("Cleanup() - complete, work outstanding");
   }
   else
   {
    Debug.WriteLine("Cleanup() - complete");
   }
  }

Might be a nicer way to implement this using TPL and certainly it could be engineered to be more reusable. TPL wraps a lot of the lower level constructs, but does have the advantage of being easier to read / maintain.


Here's the debug output for a Start, followed by a Stop...

OnStart() - Max background threads allowed =3
OnStart() - complete
DoWork()
LongRunningTask() - New #1
LongRunningTask() - New #2
LongRunningTask() - New #3
LongRunningTask() - #2 complete
LongRunningTask() - #1 complete
DoWork() - A background Task completed
LongRunningTask() - New #4
LongRunningTask() - #3 complete
DoWork() - A background Task completed
LongRunningTask() - New #3
DoWork() - A background Task completed
LongRunningTask() - New #4
OnStop()
DoWork() signals main thread complete
DoWork() - complete
Cleanup()
Cleanup() - blocking 5000ms
Cleanup() - Work outstanding, ask SCM for 5000ms additional time
Cleanup() - blocking 5000ms
LongRunningTask() - #4 complete
LongRunningTask() - #3 complete
DoWork() - A background Task completed
LongRunningTask() - #4 complete
DoWork() - A background Task completed
DoWork() - A background Task completed
Cleanup() - complete
OnStop() - complete 


Wednesday, 18 April 2012

Federated custom tcp binding (and http binding)

This is a work in progress, but so far it allows me to take a token from ACS (Azure Access Control Services) - I followed the basic SDK/MSDN advice to set up a relying party, provider and rules - and send it to a service (acting as Relying Party) by way of authentication.

I was particularly interested to try this with TCP binding and it looks like custom binding is the only way.

For the http I started with the Federated HTTP binding, but since the custom binding took shape, you get more control this way, so I formulated an https equivalent here too.

Note this is for illustration purposes it's certainly not production ready.

1. Get token from ACS
  

        private static SecurityToken GetIdentityProviderToken(string acsEndpoint, string serviceEndpoint)
        {
            var factory =
                new WSTrustChannelFactory(new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential), acsEndpoint)
                {
                    TrustVersion = TrustVersion.WSTrust13
                };

            factory.Credentials.ClientCertificate.SetCertificate(
                StoreLocation.LocalMachine,
                StoreName.My,
                X509FindType.FindBySubjectName,
                "[cert dns/hostname]");

            var rst = new RequestSecurityToken
            {
                RequestType = RequestTypes.Issue,
                AppliesTo = new EndpointAddress(serviceEndpoint),
                //specify URI realm that ACS token will apply to
                //AppliesTo = new EndpointAddress( new Uri( "urn:federation:customer:222:agent:11" ) ),
                KeyType = KeyTypes.Symmetric
            };

            factory.Credentials.UserName.UserName = ClientUsername;
            factory.Credentials.UserName.Password = ClientPassword;
            var channel = factory.CreateChannel();

            return channel.Issue(rst);
        } 


2. Http Client config (code)
 

        private static ChannelFactory GetCustomHttpBoundService(SecurityToken token, string address)
        {


            var securityBootStrap = SecurityBindingElement.CreateIssuedTokenForCertificateBindingElement(new IssuedSecurityTokenParameters());
            var security = SecurityBindingElement.CreateSecureConversationBindingElement(securityBootStrap, requireCancellation: true);
            
            Console.WriteLine("Include timestamp " + security.IncludeTimestamp);
            Console.WriteLine("Allow insecure transport " + security.AllowInsecureTransport);
            Console.WriteLine("Client: Detect replays " + security.LocalClientSettings.DetectReplays);
            Console.WriteLine("Client: Max clock skew " + security.LocalClientSettings.MaxClockSkew);
            Console.WriteLine("Server: Detect replays " + security.LocalServiceSettings.DetectReplays);
            Console.WriteLine("Server: Max clock skew " + security.LocalServiceSettings.MaxClockSkew);


            var customBinding = new CustomBinding(new List
            {  
                security,
                new BinaryMessageEncodingBindingElement(),
                new HttpsTransportBindingElement()
            });

            var factory = new ChannelFactory(customBinding,
                new EndpointAddress(new Uri(address), EndpointIdentity.CreateDnsIdentity("[cert dns/hostname]")));
            factory.ConfigureChannelFactory();

            Debug.Assert(factory.Credentials != null);
            factory.Credentials.SupportInteractive = false;
            factory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine,
                StoreName.My,
                X509FindType.FindBySubjectName,
                "[cert dns/hostname]");

            return factory;
        }

3. TCP Client config (code)
  

        private static ChannelFactory GetCustomTcpBoundService(SecurityToken token, string address)
        {

            var securityBootStrap = SecurityBindingElement.CreateIssuedTokenForCertificateBindingElement(new IssuedSecurityTokenParameters());
            var security = SecurityBindingElement.CreateSecureConversationBindingElement(securityBootStrap, requireCancellation: true);
      
            Console.WriteLine("Include timestamp " + security.IncludeTimestamp);
            Console.WriteLine("Allow insecure transport " + security.AllowInsecureTransport);
            Console.WriteLine("Client: Detect replays " + security.LocalClientSettings.DetectReplays);
            Console.WriteLine("Client: Max clock skew " + security.LocalClientSettings.MaxClockSkew);
            Console.WriteLine("Server: Detect replays " + security.LocalServiceSettings.DetectReplays);
            Console.WriteLine("Server: Max clock skew " + security.LocalServiceSettings.MaxClockSkew);

            var customBinding = new CustomBinding(new List
            {  
                security,
                new BinaryMessageEncodingBindingElement(),
                new SslStreamSecurityBindingElement {RequireClientCertificate = false},
                new TcpTransportBindingElement()
            });

            var factory = new ChannelFactory(customBinding,
                new EndpointAddress( new Uri(address),  EndpointIdentity.CreateDnsIdentity("[cert dns/hostname]")));
            factory.ConfigureChannelFactory();
       
            Debug.Assert(factory.Credentials != null);
            factory.Credentials.SupportInteractive = false;
            factory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine,
                StoreName.My,
                X509FindType.FindBySubjectName,
                "[cert dns/hostname]");

            return factory;
        }


4. Http Server config
  


        <binding name="customfedhttps">
          <security authenticationmode="SecureConversation" requiresecuritycontextcancellation="true">
            <secureconversationbootstrap authenticationmode="IssuedTokenForCertificate">
            </secureconversationbootstrap>
          </security>
          <binarymessageencoding>
          <httpstransport requireclientcertificate="false">
        </httpstransport></binarymessageencoding>
       </binding>

5. TCP Server config
  


        <binding name="customfedtcp">
          <security authenticationmode="SecureConversation" requiresecuritycontextcancellation="true">
            <secureconversationbootstrap authenticationmode="IssuedTokenForCertificate">
            </secureconversationbootstrap>
          </security>
          <binarymessageencoding>
          <sslstreamsecurity requireclientcertificate="false">
          <tcptransport>
        </tcptransport></sslstreamsecurity></binarymessageencoding>
        </binding>

6. Server behaviour
  

    <behaviors>
      <servicebehaviors>
        <behavior name="fedbehaviour">

          <servicemetadata httpsgetenabled="true">
          <federatedservicehostconfiguration>
          
        </federatedservicehostconfiguration></servicemetadata></behavior>
      </servicebehaviors>
    </behaviors>

    <extensions>
      <behaviorextensions>
        <add name="federatedServiceHostConfiguration" type="Microsoft.IdentityModel.Configuration.ConfigureServiceHostBehaviorExtensionElement, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      </add></behaviorextensions>
    </extensions>


7. Identity model config
  

  <microsoft.identitymodel>
    <service>
      <audienceuris>
        <add value="https://localhost/Service1.svc">
        <add value="net.tcp://localhost:997/Service2.svc">
      </add></add></audienceuris>

      <servicecertificate>
        <certificatereference findvalue="[cert dns/hostname]" storelocation="LocalMachine" storename="My" x509findtype="FindBySubjectName">
      </certificatereference></servicecertificate>

      <issuernameregistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <trustedissuers>
          <add name="[cert dns/hostname]" thumbprint="[cert thumb print]">
        </add></trustedissuers>
      </issuernameregistry>

      <certificatevalidation certificatevalidationmode="None">

      
    </certificatevalidation></service>
  </microsoft.identitymodel>



Useful links:








Sunday, 15 April 2012

Azure Web roles, configuring IIS - basic auth / ssl example

Windows Azure roles have the minimum configuration which covers most needs, but when you need to ensure that your deployment works with additional features some configuration is required. The best place to do this is when the web role starts. In this example I want to ensure that basic authentication is possible with my azure hosted website against a local NT account; so the basic authentication role module needs to be added to IIS7 when the role starts, I also need to unlock the config sections so that my web.config can configure the authentication (disable anonymous, enable basic authentication and allow SSL). Step 1. Create the file in the startup folder, first off allow powershell: enablepowershell.cmd - containing a single line:
  
powershell -command "set-executionpolicy Unrestricted"&

Step 2. A command file to add basic-auth and "unlock" the IIS config sections (easy one to forget!) that I require here:

configiis.cmd
  
ServerManagerCmd.exe -install web-basic-auth 

%windir%\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/security/access 

%windir%\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/security/authentication/anonymousAuthentication 

%windir%\System32\inetsrv\appcmd.exe unlock config /section:system.webServer/security/authentication/basicAuthentication 


The basic principle can be extended to cover other scenarios. Step 3. Finally, some powershell to add a user "AddUser.ps1", in this example I'll configure the basic authentication such that a local user account provides the credentials, but as such an account doesn't yet exist, I will need to create it :

  $username="test"
  $computer = [ADSI]"WinNT://localhost"
  $user_obj = $computer.Create("user", "$username")
  $user_obj.SetPassword("testpassword1!")
  $user_obj.SetInfo()
  Write-Host "$username created."


Plenty of examples out there about how to make this a bit tighter. Now for tying this in with my azure web role. First off I want to up the OS version in my azure config (.cscfg) to version 2 (2008R2 I believe)

<ServiceConfiguration serviceName="AzureMocks" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*" >
.
.
</ServiceConfiguration >


Next I need to add some sections to my service definition file (.csdef) This runs the command files under elevated privs.

    <Startup>
      <Task commandLine="Startup\enablepowershell.cmd" executionContext="elevated" />
      <Task commandLine="Startup\configiis.cmd" executionContext="elevated" />
    </Startup >
    <Runtime executionContext="elevated" />

Lastly in my web role start I call the powershell "AddUser.ps1"

            
            try
            {
                Startup.RunPowershellConfig(@".\startup\AddUser.ps1", "AddUser.ps1.txt");

            }
            catch (Exception e)
            {
                RoleDiagnosticsHelper.WriteExceptionToBlobStorage(e, @"An error occured running the powershell startup script '.\startup\AddUser.ps1'");
                return false;
            }
Where those functions are defined as
  

        public static void RunPowershellConfig(string path, string outPath)
        {
            var startInfo = new ProcessStartInfo
            {
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
                FileName = "powershell.exe",
                Arguments = path,
                RedirectStandardOutput = true,
                UseShellExecute = false,
            };

            var writer = new System.IO.StreamWriter(outPath);
            var process = Process.Start(startInfo);

            process.WaitForExit();

            writer.Write(process.StandardOutput.ReadToEnd());
            writer.Close();
        }

        public static void WriteExceptionToBlobStorage(Exception ex, string additionalInfo)
        {
            if (null == additionalInfo)
                additionalInfo = string.Empty;

            var storageAccount = CloudStorageAccount.Parse(
                RoleEnviroment.GetConfigurationSettingValue(
                  "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString"));

            var container = storageAccount.CreateCloudBlobClient()
              .GetContainerReference("rolestartexceptions");
            container.CreateIfNotExist();

            var blob = container.GetBlobReference(string.Format(
              "role-start-exception-{0}-{1}.log",
               RoleEnvironment.CurrentRoleInstance.Id,
               DateTime.UtcNow.ToLongDateString()));
            
            // tostring should include inner exception if exists
            blob.UploadText(ex + " Additional information " + additionalInfo);
        }


I am careful to ensure that I catch and write any exceptions, a problem on role start can be tricky to find and fix otherwise. See cweyers post here
Of course I should not forget, my website config needs the required settings to actually allow SSL and basic auth now all the above are complete


  <system.webServer>
    <security xdt:Transform="Replace">
      <access sslFlags="Ssl" />
      <authentication>
        <anonymousAuthentication enabled="false"/>
        <basicAuthentication enabled="true"/>
      </authentication>
    </security>
  </system.webServer>


Important note
When running all this locally in the emulator that last bit of config can cause some problems, if you spin the site or service up in the emulator the debugger may refuse to attach (we've reported this as a bug under tools version 1.6 Nov 2011 and I believe it's known. The work around is to comment the offending section and when the emulator role starts, go into IIS and configure through the management console. Can use a config transform to ensure the config really does go in for real as this issue appears to be limited to the emulator. So if you see a weird emulator error complaining about invalid site, the config is a good place to start.

Thursday, 2 February 2012

net pipes with WCF in Azure / IIS7

There are a few things worth remembering


1. It's best to leave the address of the service empty in the service side config. You may be forgiven for thinking that the address (for a net named pipe endpoint) can be anything you like, after all it's a pointer to shared memory? Yes, but not in IIS, you need to ensure your address matches the activation path
For example:
If you host a service with net named pipe endpoint and this is deployed to your machine, default web site, lets say it's called WCFService1 with Service1.svc
The address needed at the client would be : "net.pipe://localhost/WCFService1/Service1.svc"


2. Don't forget to add a net named pipe binding to the root website and enable the protocol for the IIS application. Also ensure that the WAS service for net pipe activation is running. (Powershell can accomplish all these things and see here for a nice example for ensuring an azure hosted site will start).


3. You cant (so far as I can see) have two websites in IIS that both have the net named pipe binding. The reason is probably because the activation service can distinguish between the sites by address (unlike net tcp where you could specify differing ports for each site to listen for the incoming message). This is the error you would get:



An error occurred in the Activation Service 'NetPipeActivator' of the protocol 'net.pipe' while trying to listen for the site '1', thus the protocol is disabled for the site temporarily. See the exception message for more details.
URL: WeakWildcard:net.pipe://[machine name]/
Status: ConflictingRegistration
Exception:
Process Name: SMSvcHost
Process ID: 6968




I believe Weakwildcard refers to (I believe) the WCF Hostname comparison mode
This “Weakwildcard” setting is the default in IIS hosted sites.

4. WCF defaults the context mode to per-session, both net tcp and named pipes use a session at the protocol level. In WCF therefore you cannot turn session off if you are using a contract that implements either of these bindings. Specific to net pipe, if you have a "client" which calls a "server" **one way** for example

//Client (abridged)

        public void Test()
        {
            using (var cf = new ChannelFactory(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/NetPipeTest/Service1.svc";))

            {
                var client = cf.CreateChannel();
                client.GetData();
                (client as IClientChannel).Close();
            }
        }

//Server (abridged)

    [ServiceBehavior]

    public class Service1 : IService1, IDisposable
    {
        public void GetData()
        {
// Long running
            for( var i = 0; i < 5 ; i++)
            {
                System.Threading.Thread.Sleep(1000);
            }
        }
        public void Dispose() { }
    }

The client will be blocked on the "close()" if the "GetData()" method on the "server" is long running. If Getdata() throws then a abstract exception will be generated on the client at close(). To work around this you could offload the long running part of "GetData()" to a new task, perhaps with task factory new task.

See here for some related info

Wednesday, 26 October 2011

WCF Hosting in IIS and serviceHost.Open()

We recently had a valid reason for *looking* to see if we could (when hosting WCF in IIS) override the ServiceHost.Open() method - specifically we were looking at the possibility of implementing a retry for certain exceptions thrown on open.


The most obvious course of action was to look at declaring a custom service host factory, reference this from the "factory" attribute in the .svc markup and implement our own "ServiceHost" implementation derived from ServiceHostBase.


The problem is that "open" is defined on the communication channel class and it's not virtual, we thought about redefinition by hiding (declaring a reimplementation of "open" with the the "new" keyword in our service host class) but it would appear that IIS internally takes a reference to serviceHostBase - and redefining that would be a step too far IMO.


So in short it doesn't look like it's (easily) possible, if at all, I guess not many people have a reason to consider doing this with IIS hosted services.


(We did also take a quick look at what might be possible if we hooked up to some of the events around open, but this "felt" wrong then and still does, even though it *might*  be technically possible).


This was all in context of "on-premise" services connecting to azure via the Azure "ServiceBus", in some circumstances we were getting "AddressAlreadyInUseException" - the discussion was around what it might take to implement  a "retry" if the serviceHost.Open() failed with that exception; subsequently we understand that MS intend to release changes to support load balancing in SB - in other words allow more than one connection per URI, so our issue should be resolved with this change.

Wednesday, 21 September 2011

Msbuild and VS solution/project "platform" inconsistency

If you want to build a solution using MSBUILD under a specific configuration you have to specify a valid combination of build-configuration and platform.

For example "Debug|Any CPU"

This is fine, unless you are using a composite target that works on both solution and projects; in our case we were using the solution to build some web projects and their dependent projects and then calling a target to build the web project hosts. (An alternative would have been to build from a project list, but it's convenient to utilise a solution where dependants are resolved auto-magically!)

The problem was that introducing "platform" as a parameter worked for the "build solutions" target - we build under the correct configuration, but it broke the "build web-project-package" target.

The reason is an inconsistency between solutions and projects

Solutions platform Any cpu = "Any CPU", but for projects it's "AnyCPU"

See here

As a side note:

If the solution contains an azure web role project and this project has it's own service configuration then the .csfg file is likely to be called "ServiceConfiguration.[my config name].csfg"

If this is the case you need to pass the parameter "TargetProfile=[My config name]" when building the solution, or else remove the azure web role project from the build list, which may not be desirable.

There is some default behaviour with the azure web role project build target, if a valid value for parameter "TargetProfile" is not passed then the default behaviour is to look for "ServiceConfiguration.Cloud.csfg" first, followed by "ServiceConfiguration.csfg" if neither is found there's an error.