In my team, we want to stay close to cutting edge, but not bleeding edge. We won’t be first mover on the newest technology, but as soon as the teething problems have been worked out, and the risk has been assessed, we are ready to go. That is why we decided that after v.9 had been out for a year, we should upgrade to the latest version. Of course, we would prefer to go to CRM Online, but for different reasons, we are not quite ready for that journey yet.
This post describes the way we planned and executed the upgrade, and what we learned from it. The content is not only applicable for v.9, but a lot of it is relevant for any upgrade project.
Planning for the v.9 upgrade
When upgrading CRM, you have a few options:
- In place upgrade: Run the installer on your existing servers
- Migration upgrade: Install the new version on new servers, and import/upgrade a backup of the database
- Data migration: Rebuild CRM on new servers, and migrate the data.
In general it is always recommended to do a migration upgrade over an in-place upgrade. The reason is related to risk. If an in-place upgrade screws up your environment, you’ll have to re-install your servers and restore databases to latest backups. If a migration upgrade goes wrong, you can just delete the organization and try again. There is also the added benefit of being able to do dry runs of the upgrade, and you have a solid fall back option: You just keep your DNS pointing to the old servers.
The data migration option makes sense if you have a lot of technical debt in your solution, and a lot of bad data. This is especially the case in many companies where an initial CRM implementation has been made, but they have not done the maintenance needed to keep the system up to date. When you are 4+ major versions behind, you can be sure to face a lot of breaking changes, and code that needs to be rewritten. It is in such cases I would consider starting over, and migrating the data that is valuable to you, and use the migration as an opportunity to do some data quality clean-up.
Luckily, in our case, we have a great focus on following best practices and coding features in a way that is forward- and Cloud-compatible. Therefore there was no reason to start from scratch, and the migration upgrade was the obvious choice.
Also, the old CRM was on legacy servers, where the Bank has a strategy to move to private cloud solution (in-house) for hosting. We saw this as an opportunity to not only upgrade CRM, but to redesign the system architecture into something that would give us more control, flexibility, security and scalability.
Managing scope
Upgrading Dynamics 365 can be a big task on its own, but redesigning the system architecture at the same time, on a new platform with new technologies makes this a very complex project. It became clear very early that we needed to reduce scope as much as possible.
We managed to reduce the scope of the upgrade by doing the following:
Classic UI
Most of the sexy features in Dynamics 365 is in the Unified Client Interface. However, going directly from v.8.2 to UCI would require a lot of change management and organizational implementation, and the effort would have to be done in one go up to Go-Live.
By instead going to Classic UI, we can drastically reduce the training effort, as users are able to adapt to the improved classic UI without additional training. Once upgraded, we can gradually pilot and roll out the UCI at our own pace.
Therefore, by choosing Classic UI, we reduced the upgrade project from being a project impacting the entire team at a large scale, to a technical project to be delivered by a task force.
ADFS and Outlook
ADFS is the recommended mechanism for authentication in Dynamics 365. Yet, we decided not to implement ADFS in the initial phase for the following reasons:
- We don’t have a lot of in depth experience configuring ADFS in the team
- Setting up will require a lot of cooperation with other teams, causing external dependencies and risk of delays
- Kerberos/NTLM is the fall-back for ADFS, so this has to be configured anyway.
- Connections strings for ADFS are slightly different, so by delaying the switch to ADFS, the upgraded deployment will be easier to get to work with existing integrations
- The Outlook client for v.8 is compatible with v.9, so by staying on Kerberos authentication, we don’t have to do anything about the client configuration for our many users.
SDK
Testing concluded that CRM v.8.2 SDK was compatible with Dynamics 365 v.9. Likewise, we tested that we could deploy components built towards CRM v.9 SDK to an Dynamics 365 v.8.2 environment.
This gave us the option of updating the code before upgrading CRM, upgrade the code and CRM together, or wait with upgrading the SDK until after the CRM upgrade.
We decided to wait with upgrading the SDK until after going live. However, certain tools like the solution packager needed to be upgraded to v.9 in order to work with the new environments.
Once we did upgrade the SDK, it was not trivial, because you cannot mix and match versions. Once we upgraded one part of the code, we needed to upgrade our entire codebase.
Branching and code-freeze
The challenge for us was to be able to work in a way that we could prepare the infrastructure for V.9 while developing new features for v.8.2 in parallel. However, at one point we would have to upgrade our development environment, and at this point, we would not be able to release any more features for v.8.2 until the Go-Live date for the upgrade (except for minor hot-fixes). The trick is therefore to switch over to developing on v.9 as close to the go-live as possible.
By reducing scope of the project, and having a strict focus on pre-loading tasks, we were able to reduce the time from switching development track to V.9, and to going live, down to less than two months.
By planning the upgrade like this, and doing the work in a small task force, we were able to continuously deliver value to the business while working on the upgrade in parallel. In the end we only had to skip one planned release (we release every three weeks), and by doing it this way, the upgrade was a lot more acceptable for the business owners.
So how is a migration upgrade actually done? Here is the overview of the phases once the infrastructure is in place:
- Test upgrade
- Try to upgrade a backup of prod onto the new servers
- Note down all issues, and make plans for how to cope
- Re-do test upgrade to confirm plan if needed
- Switch branching and dev environments
- Once you are confident that you can upgrade the production environment over the course of a night or a weekend, you can proceed with this step
- Upgrade the DEV (and TEST) environment
- Update the develop and release (if you have that) branches in your source control repository to contain the v.9 code and customizations
- Keep at least one non-prod environment on v.8.2, along with one branch (ie. master) to reflect the current production, in case you need to do hotfixes.
- Make all the necessary changes to your code and customization to be ready for go-live.
- Go-live
- Do the actual upgrade by backing up the production DB, and restoring it in the new environment.
- Deploy all the changes that was prepared since switching to v.9 development.
- Update the DNS entries to point to the new infrastructure
- Pop the Champagne!
- Fall-back?
- Just change the DNS back. But remember, once users get on the system, and start updating data, there is not turning back!
“v.9 Upgrade” is an infrastructure project
At least it was true in our case!
We decided to name the project “v.9 upgrade”. This was a good name, as it was easily understandable for both the business and the IT organization. “Private cloud migration” might have been more descriptive, but where is the value for the users in that?
From past upgrades I have been part of, we have had the infrastructure delivered by technical consultants or IT departments, so we could focus on upgrading the actual solution and code. Based on my previous experience, I estimated that we could do the upgrade in about three months.
However, building a complex infrastructure for an Enterprise scale CRM deployment takes time. A challenge not to be underestimated is the strict segregation of duties and specialization implemented in a huge enterprise organization. This makes a lot of sense for day to day maintenance, but when doing a big technical project, you quickly realize that being dependant on others for even the most trivial tasks can kill any progress.
In the end, we delivered the upgrade to v.9 in about 7 months, with a task force consisting of about only 1/4 of the team. The actual upgrade of the solution and code was done primarily in the last month.
Our Architecture
Our old architecture was based on virtual servers hosted in the LAN zone. We had 6 of them, and they were all deployed as full servers, containg CRM, custom API’s, custom integrations services etc. The database was deployed to physical servers for performance reasons.
In the new architecture, we have made the following improvements:
- Each environment separated from each other in different network Enclaves.
- Each server has only one function, with the following server roles:
- CRM front-end
- CRM back-end
- Custom API’s
- Integrations
- Custom UI and Apps
- The different server roles are hosted in separate network microsegments.
- Each server role is load balanced between instances in two datacentres.
- All communication in, out and between microsegments are controlled by firewall rules
- New servers are much smaller than the old ones. However, there are a lot more of them, and they can be scaled up and down as needed via the cloud portal. This makes the servers more resilient (they impact each other less), and we maintain a reasonable cost level while being able to scale up drastically if needed.
Terraform
One of the great benefits from switching to a cloud deployment was the outlook of doing infrastructure as code deployments. Our cloud solution supports terraform, and we had high hopes for using terraform, even though the implementation was fairly new.
However, terraform turned into a great source of frustration and delay for us. The main reason being that the terraform provider for our cloud solution was not mature enough at the time, and we ended up scrapping terraform after 1.5 months of trying to get it to work. The main issue was that the provider was really bad at handling changes, and would suggest to redeploy the entire infrastructure if you had changed a load balancer setting. That is not cool when you have spent weeks installing and configuring the servers.
I cannot judge terraform as a technology based on our experience on one project, using an in-house provider in a very early stage. However, I can see terraform being very useful if you need to spin up new environments with the same configuration often. If you have a complex infrastructure, and you will doing more changes than complete redeploys, I would at least be more careful before adopting the technology. It would be great in a disaster recovery scenario, to completely rebuild an environment, but you have to assess if the amount of work is worth it. And sometimes it is almost as easy to provision the components by hand when you have a cloud self service portal, and a good guide. For us, we might revisit terraform later, once other have had more success with it, but for now it is buried.
Issues upgrading to Dynamics 365 v.9
Windows Server 2016 and SQL Server 2016
There are a few things you need to be aware of when installing Dynamics 365 on the 2016 version of Windows Server and SQL server:
- Dynamics 365 pre-requisites: You need to install the 2012 version of SQL Server Native Client, even though the other SQL assemblies should be the 2016 version (CLI tools and management objects).
- AES encryption: On Windows Server 2016, your installation user need to have AES encryption enabled in order to install CRM. Otherwise you will get the below error message. You will also need your domain admin to enable AES encryption on the service account for the CRM application pool, or Kerberos authentication will not work for CRM.

Kerberos
Kerberos requires some experience to set up, and luckily I have done it before. At the same time, we had the old v.8.2 environment where it was working, so it should be easy enough, right?
In reality, setting up Kerberos was a b**ch…
Kerberos is a very secure provider for Windows authentication. In IIS, Kerberos is the Windows Authentication provider called “Negotiate”. It allows single sign on in an intranet environment.
For Kerberos to work, you need to register SPNs (Service Principal Name) for the service account of the application pool. The SPN is registered in the Active Directory, and tells the AD which domain user is allowed to encrypt and decrypt Kerberos access tickets, for a given DNS name. It is not allowed to have duplicate SPN entries, so that means that any given DNS entry can only be backed by a single service account.
In a load balanced deployment, that means that all front end servers must use the same service account. At the same time you need to configure IIS to use the application pool credentials for Kerberos, and not the machine account. Otherwise each front end server will try to authenticate users using their own machine account, which would be different for each server, and not matching the SPN. You can do this in IIS by opening the Configuration Editor, navigating to system.webServer/security/authentication/windowsAuthentication and setting the parameter useAppPoolCredentials to true.
There is another setting in the same area called useKernelMode. Kernel mode is an easier way to set up Kerberos, where you don’t need to have SPNs. It is using the local machine account to do this, so it makes sense that this setting should be false when useAppPoolCredentials is true. However, after much trial and error, we found out that Kerberos would only work in our set up if both parameters were set to true.
The next issue we faced was that the load balancers in the new cloud environment were not the same as the ones in the old environments. Apparently, the default setting on AVI loadbalancers is to use connection multiplexing. Connection multiplexing is not a feature that plays well with Kerberos and NTLM. Unfortunately, this was not a setting we could just disable in the cloud portal, and we had to go through a long process including a very long waiting time, to get to a network admin that could do it for us (without even knowing if that would fix it for us).
The last issue we had with Kerberos is already mentioned above in the last section. Apparently Kerberos works different on Windows Server 2016 than on different Windows versions. For it to work, we needed to get someone to enable AES 128 bit encryption and AES 256 bit encryption on the service account of the CRM application pool.
The biggest issue I have with Kerberos is that it is impossible to debug. It either works, or it does not. If you have more than one thing not working in your setup, you can try endless combinations before getting anywere. My advice? Talk to an expert!
Permissions, permissions, permissions…
Because we wanted to potentially automate the installation of Dynamics 365, and also control what AD groups was used for CRM, we used an unattended command line installation, using a configuration file:
In order to control the AD groups created, you need to set the auto group assignment to false in the configuration file. This will however also prevent the installer from assigning all required privileges to the service accounts. We also had issues getting the installer to accept service credentials in the config file, so we ended up installing with default credentials (Network Service) and changing the service users in Services and IIS afterwards.
To get away with that, you need to be sure that you have assigned the correct privileges yourself. Luckily, Microsoft has documented the least amount of privileges needed:
However, when we were done installing, we discovered quickly that following Microsoft’s recommendations were not enough to run Dynamics 365. We found the following issues with the recommended permissions:
- Application Service (IIS app pool user) needs to be granted log on as a batch on back-end servers, or the application will fail upon iisreset. The CRM website is not in use on back end servers, so we discovered this after a Windows patch that restarted IIS, and caused the CRM app pool user to be locked out of the AD!
- Sandbox processing service will not start with recommended permissions by MS. We ended up having to uninstall the sandbox service, and re-install it via the uninstall/configure wizard in program files. That way, we were able to re-install it from the UI, where we could type in the correct credentials, and have the wizard apply missing (and undocumented) permissions. We never found out what permissions were actually missing.
- Async service will run with documented permissions, but will generate errors in trace files and event viewer. By re-configuring the async service in the UI wizard (like with the sandbox service) we managed to get it working.
Manually assigning permissions is painstaking compared to just clicking next in an installer. However, I have found one interesting thing through this experience: On many deployments that I have seen before, there have been many warnings about failure to generate CRM keys in the event viewer. For example, the old environment on v.8.2 was not able to generate keys in the config database unless the async service was restarted every night. However, after properly assigning permissions (UI installer + manual assignments) to the async and async maintenance services, these errors have disappeared in the new environments.
Patches
So which patch should you apply when upgrading?
We have experienced some serious issues with different versions of Dynamics 365 v.9.
With version 9.0 (installation files), it is not possible to create new organizations or import old organizations. Apparently it is only good for in-place upgrades.
We managed to successfully upgrade from v.8.2 by applying update 0.6 (9.0.6) to our servers. This was a fairly stable release, but during final testing, we found some severe bugs that were blocking for us going live. For example, there were several views, dashboards and charts that did not render properly. Especially dashboards looked like crap, with dashboard elements jumping around randomly, creating misalignment and whitespace.
As a last resort fix, we applied update 0.12 (9.0.12) just before go-live. That fixed a lot of the UI issues. However, when doing test upgrades, you should always do it on the version you plan to go live on. We learned that the hard way during the Go-live weekend when it turned out that version 9.0.12 was not to happy about importing old databases much like version 9.0. Luckily we was able to work around it by temorarily downgrading a single back-end server to version 9.0.6, and then proceeding with the deployment.
Based on the issues we have had with the different patches, it is clear that Microsoft is not putting it’s strongest focus into the on premise version of CRM.
Testing and bugs
Microsoft always tells us to stop coding and use configuration instead. That way you are guaranteed to do things supported, and things will not break when upgrading.
Well, we have quite a lot of custom code in our deployment, but I am quite proud of the quality we put into our code, and so I was not that concerned about breaking changes. Also, we went for the Classic UI instead of Unified Client Interface, so it was pretty much v.8.2 with some new CSS, right??
Even so, we did quite a lot of testing, primarily focussed on our custom code, and integrations. We were so naive…
The initial finding revealed that there were not big issues, like expected. However, during the final weeks before Go-live, when the whole team had been moved over to v.9 in the development environment, we started getting all sorts of bug reports.
To our astonishment, more or less all the bugs was not at all related to our code, but bugs in the platform!
Examples of error we found on Dynamics 365 9.0.6 during the final two weeks before go-live:
- A view will not render if you have added customer JS to it (any JS), and one of the columns is an activity party.
- Workaround: Remove either JavaScript or PartyList column.
- The field pipeline phase on opportunity will be calculated with incorrect number prefix
- Workaround: Replace the field with a custom field, controlled by plugin and one-time job.
- CRM throws and error when you try to resolve a Case and there is a RetrieveMultiple plugin registered on ActivityPointer, that access the InputParameters on the plugin execution context.
- Reported to Microsoft. Should be fixed by version 9.0.13
- CRM throws “An error occurred while the chart was rendering.” when creating a chart with a horizontal label that points to a field from Owning User and the view contains a criteria on Activity Parties.
- Reported to Microsoft. They have promised that this will be fixed by 9.0.14, to be released in March.
Learnings regarding testing:
- Just because Microsoft calls something a facelifted UI, does not mean you should assume that it is working the same way as the previous version, and required less testing than UCI.
- Just because you do something “best practice” does not mean it cannot break during an upgrade. That is true for custom code, and that is apparently also true for something as simple as a view.
- Don’t underestimate testing, even though it is tempting as a tech person, when you are in a hurry, and running out of time.
- What is true in one version may not be true in another. When you install a patch, you cannot trust any previous test results. Test over.
- Don’t test your custom code (alone). Test what impacts the users and customers.
Thanks to a huge team effort in the final weeks, we managed to go-live without any big issues, and make v.9 a success!

