Friday, September 12, 2008

Managing Custom pages in IIS

I have recently been introduced to some new ways to manage custom webpages that I had no idea was readily available to me. I assume that everyone has seen those w3wp.exe processes in Task Manager, I knew that they were called Worker Processes but I didn’t realize that every Application Pool in IIS generates its own Worker Process (a w3wp.exe process in Task Manager). That being said simply by giving each page (CRM and custom pages) their own AppPool you can see any potential memory problem s, or where the largest load to IIS is among your pages. Secondly there are options on the AppPool that you can change to help as well. Here are some screenshots:




So, something like shutting down our Worker Processes on custom pages that are used as buttons after being idle for 5 minutes instead of 20 could make a big difference in a system that is already strapped for memory. Scheduling a recycle of a specific page (doing an IISRESET for just that one page) could also make a big difference. Some pretty cool features that make managing memory on our webpages that I had no idea were available to us. This may be something to put in your back pocket for a rainy day but hopefully this comes in handy for someone else later on.

David Fronk
Dynamic Methods Inc.

Friday, September 05, 2008

Methods for mass updating records in MSCRM

Working with MSCRM and from my background I have typically always been a web programmer as have my colleagues. This is essential in the MSCRM world if you want to be able to customize anything from form scripts to placing your own page that does crazy things right into the middle of the MSCRM GUI.

However, when presented with the task of needing to mass update data I have found that there are sometimes better ways than a web page. I have had minimal experience with application programming and programming an executable used to be a mystery to me.

A need presented itself for one of my clients where a procedure was needed to be rerun on a scheduled basis. Most of my previous procedures of this type were custom web pages with a "Go" button that ran all of my code. I quickly realized that I couldn't schedule a pressing of my "Go" button and I needed another solution. An executable was the solution.

I was scared that I was going to have to spend the better part of a week fighting and learning new calls and commands but amazingly enough, the majority of my code (the calls to the MSCRM API's) was all the same. For me, the learning curve into non-web programming came relatively quickly and there were a lot of benefits. There are benefits to both worlds so don't think that I've gone away from mass updating via web pages because that's not the case. Here are some pro's and con's that I have run into when developing a solution for a web based or console based solution.

Web Based Solution
Pro's: If you are already a web developer then you have all the skills required. Web pages can also be used externally very easily where console applications are a bit more difficult. One other advantage to web pages is that they are great to if you want your end users to access them and use them. Things like, update addresses for contacts related to a given account, or moving records from a sales rep's territory to a new territory owner are great for end user usage.
Con's: You have to set up the web page. If you don't build security around it everyone can see it and possibly use it. It cannot be scheduled, it must be run manually.

Console Based Solution
Pro's: Other than the coding, there is no additional set up, you just double click the executable or run it from a command prompt and away it goes. You can set up Scheduled Tasks that can run your code whenever you tell it to be run. Nightly, hourly, weekly, yearly, whatever. Also, if you build your code dynamically enough you can take your code from one installation to another and have it run just the same (authentication would have to be set up to be entered when run). Your code is compiled and no one can read your code unless you give it to them. Web pages have the ability to "View Source" and that might show off more than you want people to see.
Con's: Running externally requires that credentials be hard coded into your code. Most end users don't like running executables because they think it will install something on their machine and they don't understand what the code is doing. Black console screens (where you type in commands) scare end users. They have this mental block where if it's not in a GUI they don't understand it and would prefer not to have anything to do with it so that they don't break the system or their computer.

Well there you have it. Decide for yourself, each implementation is different so each usage can be very specific. I have a consultant's point of view where I have to be ready to do something for one client and then turn around and do something extremely similar but with minor differences for someone else and these differences between web based and console based applications can make all the difference.

Happy coding!

David Fronk
Dynamic Methods Inc.

Friday, August 29, 2008

Starter Execution Method

I have found that half of my trouble in writing a Plugin has been to figure out what message to use and how to pull the GUID from the record that the Plugin was triggered off of. Most of the messages are reasonably straight forward (Assign, Create, Update, or Delete) but finding what to use when an Opportunity closes took some digging. You'd think it was the SetState message but that's not right (nor is it the SetStateDynamicEntity).

In order to help out with this I have written myself a "Starter Execution Method" that has most of the common messages that I tend to use. I haven't coded against every message as there are now a ton of options but some of the new messages available to us are pretty cool.

Below the starter Execution Method I have also posted the possible MessageName's from the SDK. Hopefully this helps some people started.


Starter Execution Method:

public void Execute(IPluginExecutionContext context)
{
string opportunityid = "";

switch(context.MessageName)
{
case "Create":
if (context.OutputParameters.Properties.Contains("id"))
{
opportunityid = context.OutputParameters.Properties["id"].ToString();
}
break;
case "Update":
if (context.InputParameters.Properties.Contains(ParameterName.Target) && context.InputParameters.Properties[ParameterName.Target] is DynamicEntity)
{
DynamicEntity entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];
opportunityid = ((Key)entity.Properties["opportunityid"]).Value.ToString();
}
break;
case "SetState":
if (context.InputParameters.Properties.Contains("EntityMoniker"))
{
if (context.InputParameters.Properties.Contains("State"))
{
entity = (Moniker)context.InputParameters.Properties[ParameterName.EntityMoniker];
opportunityid = entity.Id.ToString();
}
}
break;
case "SetStateDynamicEntity":
if (context.InputParameters.Properties.Contains("EntityMoniker"))
{
if (context.InputParameters.Properties.Contains("State"))
{
entity = (Moniker)context.InputParameters.Properties[ParameterName.EntityMoniker];
opportunityid = entity.Id.ToString();
}
}
break;
case "Win":
opportunityClose = (DynamicEntity)context.InputParameters["OpportunityClose"];
Lookup WonLook = (Lookup)opportunityClose.Properties["opportunityid"];
opportunityid = WonLook.Value.ToString();
break;
case "Lose":
opportunityClose = (DynamicEntity)context.InputParameters["OpportunityClose"];
Lookup LostLook = (Lookup)opportunityClose.Properties["opportunityid"];
opportunityid = LostLook.Value.ToString();
break;
case "Assign":
if (context.InputParameters.Properties.Contains("Assignee") && context.InputParameters.Properties["Assignee"] is SecurityPrincipal)
{
Moniker assignEntity = (Moniker)context.InputParameters.Properties["Target"];
opportunityid = assignEntity.Id.ToString();
}
break;
case "Delete":
if (context.InputParameters.Properties.Contains("Target"))
{
Moniker monikerentity = null;
monikerentity = (Moniker)context.InputParameters.Properties[ParameterName.Target];
opportunityid = monikerentity.Id.ToString();
}
break;
}
}

MessageName Class (CrmHelpers)

AddItem
AddMember
AddMembers
AddMembersByFetchXml
Assign
Book
Clone
CompoundCreate
Create
Delete
DeliverIncoming
DeliverPromote
ExecuteWorkflow
ExecuteWorkflow
GrantAccess
Handle
Lose
Merge
ModifyAccess
RemoveItem
RemoveMember
RemoveMembers
RemoveMembersByFetchXml
Reschedule
Retrieve
RetrieveExchangeRate
RetrieveMultiple
RetrievePrincipalAccess
RetrieveSharedPrincipalsAndAccess
RevokeAccess
Route
Send
SetState
SetStateDynamicEntity
Update
Win

Happy Plug-in coding!

David Fronk
Dynamic Methods Inc.

Friday, August 22, 2008

MergeRequest/Response Limitations

This week one of my team members and I have been working through cleaning up duplicate data inside of CRM in such a way that we limit the amount of human interaction with the list of duplicates. When you have more than 150,000 records having someone manually merge even 10% of that is 15,000 records. Even if a list is provided of all of the duplicates that's a full time job for the better part of a year.

By coming up with acceptable matchcodes we figured out how to programmatically merge the records so that there doesn't have to be any human interaction. It works really well, except for one point. When you manually merge two records you get a little checkbox that selects any fields that the child record has data in but the parent record does not and then it populates the parent record with those values from the child record. That bit of code is only in the script on that form. It is NOT in the SDK. I'm working on the best way to go through and push all of the fields that the child has but the parent doesn't to make them populate the parent but unfortunately the project I'm on is a little time sensitive and I may just have to hack through it for an entity and go back later and come up with a reusable solution.

Anyway, just wanted to get that out there in case anyone else was wondering like I was until I saw my first record merge through the SDK.

David Fronk
Dynamic Methods Inc.

Friday, August 15, 2008

Integration with LaserApp

LaserApp is an application that assists in filling out forms. Typically these are forms that need information from a given person and he/she needs to sign the document. LaserApp helps save time by taking saved contact information and pushing it into their large repository of legal, financial, and other forms. Since LaserApp is keeping track of contacts and then pushing it's own contact data into the form wouldn't it be nice if you could use your CRM system to feed the forms provided by LaserApp.

We have bridged this gap at Dynamic Methods and will be participating in the LaserApp conference this upcoming week.


Basically from a high level standpoint, a button gets added to your contact form (or whatever form you would like) and when you press it, you are asked what form(s) you would like to push data to, click submit and you are then the system opens the new LaserApp file that gets created. Opening the file opens the LaserApp client which in turn loads the specified forms with the appropriate data.


Here are some screenshots:



David Fronk
Dynamic Methods Inc.

Friday, August 08, 2008

MSCRM Server Performance Counter Errors

In the last week or two I've gotten bombarded with Performance Counter information. Some was voluntary, some was forced due to errors on client's servers. I am by no means an expert but I do have to say that I have gleaned a fair amount of information that I bet the typical MSCRM admin or consultant doesn't know about. There just isn't much information out there specifically about Performance Counters within MSCRM.

My post last week talked about running the lodctr /R command. I found a post that stated that running just lodctr /R will rebuild all system Performance Counters and thinking this would clean things up I ran it on a server...not smart. At least not smart in the MSCRM world. Everything worked just fine, including CRM, until I ran an IISRESET later in the day and then CRM just died. I found articles on how to manually rebuild Performance Counters (http://support.microsoft.com/kb/300956) and that works just fine for Windows Server Performance Counters just fine. But not MSCRM. There is even a KB article in PartnerSource about Performance Counters but it says that running a repair would work just fine and resolve the problem. Well, I'd tried that before I'd even found the article and about 70% of the way through the repair it would error out with (the all too familiar error to me by now):

The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.

Only this was on the server and what I did to fix the client was what caused the problem. So, running it again (which I tried) was zero help.

After many, many hours of deliberation I finally spoke with Adam in Australia from MS Support and he helped clear up everything and demistify Performance Counters for me. There are 4 important registry keys that need to be set up correctly in order for Performance Counters to work properly in MSCRM. Oh, and for whatever reason, if the Performance Counters fail then the whole application fails (as if I didn't realize that by now). But most other applications just allow them to fail and move on. They aren't system critical, they just track the performance of the application. Adam has submitted the request of Performance Counters not being a system requirement in future versions, hopefully they listen to his request and/or read this post.

Anyway, back to the fix. In the registry there are 3 keys that need to exist, all are under the following paths:

My Computer\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services (add the last two hives of each hive listed out below to get to the actual keys)

1. CRM Async Service
2. CRM Authentication\Performance
3. CRM Discovery\Performance
4. CRM LocatorService\Performance
5. CRM OutlookSync\Performance
6. CRM Platform\Performance
7. CRM Server\Performance
8. CRM Sql Governor\Performance
9. CRM Sql Governor Multi Instance

(Basically and hive under the Services hive that has CRM in it is what you need to check.)
Oh and one more for workflow performance

10. Windows Workflow Foundation 3.0.0.0

The keys that must be there and their values are:

Key--------------------------Value
Library----------------------netfxperf.dll
First Counter----------------(any value is fine, it just needs to exist)
Last Coutner----------------(any value is fine, it just needs to exist)

Running a repair on CRM will fix all of the Performance Counters for the CRM hives but it won't fix the Workflow hive. So, if you get the error regarding Performance Counters in the middle of your repair I can almost guarantee that your problem is that you are missing keys in your Workflow Performance hive. That's what I had to do and according to Adam (who has written the majority of the Performance Counter KB articles for the MSCRM Support team) only the Workflow Performance Counters would cause the repair to fail since the repair rebuilds them.

Now that we know what needs fixed, how do we fix it? If its CRM Performance Counters, just run a repair on the server. If you error during the repair with a Performance Counter error then you will need to reload the Library, First and Last Counter keys for Workflow Foundation. In order to do this you must use the lodctr command. DO NOT recreate these manually. I haven't tried it so I can't speak to what bad things could occur if you did I just know that you should rebuild them using the lodctr command.

You need to open a command prompt and go to the following directory:

%SystemRoot%:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation

Then run the following command:

lodctr perfcounter.ini

That will rebuild the Workflow Foundation Performance Counters (which MSCRM is dependant on). If you look at the registry you should now see the keys there with correct values. Continue your repair, or try it again and you should be all set. I would recommend a reboot after the repair in this case to get everything reset and you should be back up and running.

For those of you who don't know what Performance Counters are used for, they can be very useful in showing the performance of the server and where the majority of the load is so that you can appropriately manage the server/network and correct the bottleneck of CRM. To view them open a run prompt and type "perfmon". This will open the Performance Monitor. By default it shows Pages/sec (if IIS is installed), Avg. Disk Queue Length, and % Processor Time. You can add other Counters to this list by right clicking in the list of counters at the bottom of the main section of Performance Monitor. Choose "Add Counters..." In the new window that comes up expand the "Performance object" dropdown and pick the application/object you want to monitor. Below that will be all of the counters you can monitor. Take a look at those and you'd be amazed at what you can track for CRM. There are also a lot of really good SQL ones for your SQL server. If you are looking for metrics to show companies the performance increase by going to MSCRM 4.0, or moving to a 64 bit SQL box, this will give you a great comparison and actually show the big wigs that their decision made a big difference.

I have learned a lot about Performance Counters and hopefully my lengthy post has helped someone else glean a little more information as well.

David Fronk
Dynamic Methods Inc.

Sunday, August 03, 2008

Outlook client Offline Performance Counter error when attempting to go Offline

When going offline users see the following message:

The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.

Event Log Errors:

Event Type: Error
Event Source: LoadPerf
Event Category: None
Event ID: 3013
Date: 7/29/2008
Time: 11:38:09 AM
User: N/A
Computer: LFISKNBXP
Description:
Unable to update the performance counter strings of the 009 language ID. The Win32 status returned by the call is the first DWORD in Data section.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
Data:
0000: 43 02 00 c0 17 0d 00 00 C..À....

Event Type: Error
Event Source: LoadPerf
Event Category: None
Event ID: 3009
Date: 7/29/2008
Time: 11:38:09 AM
User: N/A
Computer: LFISKNBXP
Description:
Installing the performance counter strings for service CRM Client (%2) failed. The Error code is the first DWORD in Data section.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
Data:
0000: 43 02 00 c0 f6 13 00 00 C..Àö...

Event Type: Error
Event Source: MSCRMPerfCounters
Event Category: None
Event ID: 17188
Date: 7/29/2008
Time: 11:38:09 AM
User: N/A
Computer: LFISKNBXP
Description:
The description for Event ID ( 17188 ) in Source ( MSCRMPerfCounters ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help and Support for details. The following information is part of the event: Client.

Event Type: Error
Event Source: MSCRMOfflineSync
Event Category: None
Event ID: 6000
Date: 7/30/2008
Time: 10:30:28 AM
User: N/A
Computer: LFISKNBXP
Description:
An error occurred during Offline Synchronization. Try going offline again, or restart Microsoft Outlook. Exception happened The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly..
Stack Trace Info:
at System.Diagnostics.PerformanceCounter.Initialize()
at System.Diagnostics.PerformanceCounter..ctor(String categoryName, String counterName, String instanceName, Boolean readOnly)
at System.Diagnostics.PerformanceCounter..ctor(String categoryName, String counterName, Boolean readOnly)
at Microsoft.Crm.CrmPerformanceCounterFactory.LoadCounters(CounterContainer counterContainer, PerformanceCounterLoadSetting settings, String component)
at Microsoft.Crm.CrmPerformanceCounterFactory.LoadCounters(PerformanceCounterLoadSetting settings, String component)
at Microsoft.Crm.Performance.OutlookPerformanceCounterTracker.Initialize()
at Microsoft.Crm.Performance.OutlookPerformanceCounterTracker.Increment(OutlookPerformanceCounterType type)
at Microsoft.Crm.Application.Outlook.OfflineSync.SyncData.SyncInfoArray(String syncInfo)
at Microsoft.Crm.Application.Outlook.OfflineSync.SyncData.MoveData(String syncInfo, ITransferDataProvider transferDataProvider, Int32 progressSteps)
at Microsoft.Crm.Application.Outlook.OfflineSync.SyncData.Sync(ITransferDataProvider transferDataProvider, Int32 callPriority)
at Microsoft.Crm.Application.SMWrappers.OfflineSync.SyncThreadMethod() .

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.



Fix:

Uninstalled CRM client and then ran “lodctr /R:loadperf” on client machine from a command prompt. Reinstalled CRM client and went offline successfully.


Hope this helps someone else out as well.

David Fronk
Dynamic Methods Inc.