One of the major pains with custom workflows is storing configuration information, especially configuration data that is specific to the environment dev/stage/production. There are a couple options:
- Use a custom entity to store configuration information and retrieve the records during the execution of the workflow.
- Advantages
- Allows flexibility for adding new configuration settings
- The settings are located within CRM, so there are less points of failure.
- Has the ability to store more secure configuration settings, but security must be locked down correctly so non-administrators have no access to the data. Also, you must ensure that the web service calls runs as an administrator user and not under the originating callers context.
- Disadvantages
- Is the most work to develop and deploy.
- Use the input parameters of the custom workflow to store configuration information
- Advantages
- It is the easiest to configure and deploy.
- There is no extra deployment of files to worry about and no need to create extra entities.
- Disadvantages
- Anyone can see the configuration setting, even non-privileged CRM users, so don't use it for passwords.
- A third option is to use a config file by manually loading it from a file location on the CRM server. One can specify the path of this config file in an input parameter if need be.
Configuration config = ConfigurationManager.OpenExeConfiguration("C:\\myconfig.config");
To use this option the config file must be deployed to every CRM server in the server farm and the file needs read access to the async service's account. Click here for more information: http://social.microsoft.com/Forums/en-US/crmdevelopment/thread/9d858c5d-0a31-4aaf-a1a0-195b89cb1100/
- Advantages
- It is easier to develop code using a .Net config file.
- Settings can be added and removed more quickly.
- The location is more secure than input parameters.
- Disadvantages
- It is another point of failure.
- Files must be deployed to the correct location and the service must have access to the file.
Thanks to Andriy Butenko, CRM MVP for his help in identifying the alternatives to using input parameters.
Considering all the options, I usually use input parameters. They are easy to configure and they are evident to the users. Granted, they aren't good for storing user credentials (not secure), but they do work well for passing external web service URLs, file paths to drop XML files, ect. My biggest concern with using this method is managing settings that are environment specific. For example, if you have a URL for a custom web service used to send data to your ERP system, you need to change it depending on if you are testing in dev, stage, or production. Workflows can easily be deployed to production without any thought of updating an input parameter. Before you know it, you have serious data issue because you are pushing production data to development. Not Good!!!
I am going to lay out an option for configuring all three settings within your workflow. As opposed to putting your user/administrator in charge of pointing to dev/stage/production, your workflow will be environment-aware. As an example, I am going to use the URL generator that is a part of the business productivity accelerator (http://crmaccelerators.codeplex.com/releases/view/26691).
The URL generator, returns the URL for the
current record that can be used within a send email workflow step. The
workflow uses input parameters, specifying the URL and the text for the
hyperlink. As with the issues noted previously, the problem you run into is managing the URL between environments. If you set the URL to production then users will open up production records while testing. If you set the URL to development, then you may forget to point the URL to production when moving customizations.
As a work-around I have extended the workflow to include three URLs, one for development, stage, and production. The code then looks for keywords in the machine name that the workflow runs from.

Behind the scenes, the code uses the machine name and/or organization name to determine which URL to use.
Solution #1
The first solution uses the machine name explicitly. It assumes that your development server name contains the letters "dev' and the stage server name contains the letters "stage" (if you have a stage server). If neither case is found then the production URL is used. Note: You may need to tweak the code depending on your environment. If you are only using this for one CRM installation, then you could just as easily specify the entire server name for each server level. I keep it generic so it can be used for multiple clients (with small tweaks).
private string GetURL(string machineName)
{
if (!string.IsNullOrEmpty(this.StageUrl) &&
(machineName.ToUpper().Contains("STAGE")))
return this.StageUrl;
else if (!string.IsNullOrEmpty(this.DevUrl) &&
(machineName.ToUpper().Contains("DEV")))
return this.DevUrl;
else
return this.ProdUrl;
}
Solution #2
In most cases the dev and stage orgs are on the same server, so solution #1 won't work. As a work-around, you can look for the name of the organization when determining if you are pointing to stage.
The workflow context includes the ID of the organization so you can do a simple web service call to return the organization name. With the name you can determine what URL to use. The below example will return the correct URL assuming the following. The stage org name includes the letters "stag". If the org name does
not include the letters "stag" then it checks the server name to see if
it contains the letters "dev". If neither case is satisfied then the
production URL is used.
- Development
- Server name = inetiumcrmdev40
- Org name = Inetium
- Stage
- Server name = inetiumcrmdev40
- Org name = InetiumStaging
- Production
- Server name = inetiumcrm40
- Org name = Inetium
private string GetURL(string machineName, IContextService context)
{
if (!string.IsNullOrEmpty(this.StageUrl) &&
(machineName.ToUpper().Contains("STAG")
|| GetOrgName(context.Context.OrganizationId, context).ToUpper().Contains("STAG")))
return this.StageUrl;
else if (!string.IsNullOrEmpty(this.DevUrl) &&
(machineName.ToUpper().Contains("DEV")))
return this.DevUrl;
else
return this.ProdUrl;
}
// Return the org name for set id
private string GetOrgName(Guid orgId, IContextService context)
{
using(ICrmService service = context.Context.CreateCrmService())
{
ColumnSet set = new ColumnSet(new string[]{"name"});
organization org = (organization) service.Retrieve(EntityName.organization.ToString(), orgId, set);
return org.name;
}
}
The two full solutions are available for download below.
Steps to Installation:
- Register one of the two attached solutions (.cs files) within a workflow assembly
- Restart the CRM Async Service (so CRM picks up the changes)
- Create a new workflow that uses the newly installed Custom Workflow

As a first step, execute the custom workflow step to create the URL text.

Next, set the input parameters, documenting the dev, stage (if applicable), and production URL, along with the URL Text. Be sure to include the string {0} where you want the current records ID inserted.
Note: You can also use the account name as the URL text. In this example I used the literal string "Open Account".

Add a send email step.

Specify the email message content including the output parameter of the workflow step.
Note: I kept this example very basic. It is a good idea to include verbose information in emails, such as the name of the account, primary contact, ect.

Create a new account and test out the workflow. Hover your mouse over the hyperlink and verify the URL.

You may be thinking to yourself that this is an awful lot of work just to keep your administrators from having to set a couple of input parameters when moving between environments. Fair enough! That being said, I sleep better at night knowing there is one less step to do when moving out new customizations (and one less thing that can be overlooked).
You can download the two solutions in the zip file below... ...
Enjoy!
-Andrew
Posted
03-01-2010 6:31 AM
by
Andrew Zimmer