
In this example we simply have a Visual Studio solution and a couple of projects, which we use as part of the TFS build definition. One project will contain all of the deployment files and a batch file for exporting the solution from our local or master development environment. The other project is a simple coded UI smoke test project that we run after we have deployed to the continuous deployment test environment. Of course in a real world example we would have a number of Plugins and Workflow Assembly class library projects as well as the coded UI and deployment projects, or maybe even the Deployment project from the CRM Toolkit, which is part of the Microsoft Dynamics CRM SDK.
Note: At the time of writing this the CRM Toolkit isn’t available for Visual Studio 2013, however the version that is available for Visual Studio 2012 can be used by making some modifications to it.
The Build Definitions
We need two build definitions for our automated build and deploy process. The first is a standard build definition, this basically compiles up any code that we have and copies the output of the code along with any scripts that we need to use to the output drop folder on TFS. If we have a project that use the CRM Toolkit, which is part of the CRM SDK, then as part of the build process, we can deploy the built Plugins and Workflow Assemblies to the master development environment via the command line script:
@call :GetVSInstallDir
SET PATH=%VSINSTALLDIR%Common7\IDE\;
devenv CrmSolution.sln /deploy "debug" /project CrmPackage
@REM -----------------------------------------------------------------------
:GetVSInstallDir
@set VSINSTALLDIR=
@call :GetVSInstallDirHelper32 HKLM > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper32 HKCU > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper64 HKLM > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper64 HKCU > nul 2>&1
@exit /B 0
:GetVSInstallDirHelper32
@for /F "tokens=1,2*" %%i in ('reg query "%1\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v "12.0"') DO (
@if "%%i"=="12.0" (
@SET "VSINSTALLDIR=%%k"
)
)
@if "%VSINSTALLDIR%"=="" exit /B 1
@exit /B 0
Then we can add the script as a Post-build script to our TFS build definition.
The second build definition, is the automated deployment build definition. This might seem a little confusing as I have just said that we can deploy to the master development environment using the build definition. Well we have… but that would have been an unmanaged CRM Solution, and the idea of having automated deployments is that we replicate what we want to deploy to a production environment, in a similar if not the same way manner as we are going to do the production deployment. So our case this would need to be a CRM managed solution, this also means that if we schedule the automated deployment build definition three times a day, we will have tested a lot of times during the lifecycle of a project and fixed any issues along the way.
Exporting the CRM Solution
In order to be able to deploy the CRM Solution as part of the deployment build definition, we need to export it from our local or master development environment. So we have a simply batch file which utilises our TSG.MSCrm6.Tools.SolutionManagerCLI executable.
The script is as follows:
ECHO OFF
@call :GetVSInstallDir
ECHO.
SET url=http://serverurl:5555/Organisation
SET domain=DDEV
SET username=CRM.Admin
SET password=Password
SET ConnectionString="Url=%url%;Domain=%domain%;Username=%username%;Password=%password%"
SET CURDIR=%CD%
SET PATH=%CURDIR%\Deployment\_SolutionManager;%VSINSTALLDIR%Common7\IDE\
ECHO Checking Files Out
tf checkout %CURDIR%\Deployment\Solutions\ /lock:checkout /recursive
rmdir /s /q "%CURDIR%\Solution"
ECHO.
ECHO Get Latest Solution
TSG.MSCrm6.Tools.SolutionManagerCLI.exe /operation:export /crmconnectionstring:%ConnectionString% /solutionname:MySolution "/solutionfilepath:%CURDIR%\Deployment\Solutions\3 Solution_0_1_managed.zip" /managed /logfilepath:%CURDIR%\Deployment\Logs\
IF errorlevel 1 GOTO ERROR_HANDLER
"%CURDIR%\Deployment\_SolutionManager\SolutionPackager.exe" /action:Extract "/zipfile:%CURDIR%\Deployment\Solutions\3 Solution_0_1_managed.zip" "/folder:%CURDIR%\Solution"
ECHO Checking Files Out
tf checkout /lock:checkout /recursive %CURDIR%\Solution\
tf add %CURDIR%\Solution\ /recursive /noignore
PAUSE
EXIT
:ERROR_HANDLER
ECHO.
ECHO An Error has occurred, Installer has been stopped
PAUSE
@REM -----------------------------------------------------------------------
:GetVSInstallDir
@set VSINSTALLDIR=
@call :GetVSInstallDirHelper32 HKLM > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper32 HKCU > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper64 HKLM > nul 2>&1
@if errorlevel 1 call :GetVSInstallDirHelper64 HKCU > nul 2>&1
@exit /B 0
:GetVSInstallDirHelper32
@for /F "tokens=1,2*" %%i in ('reg query "%1\SOFTWARE\Microsoft\VisualStudio\SxS\VS7" /v "12.0"') DO (
@if "%%i"=="12.0" (
@SET "VSINSTALLDIR=%%k"
)
)
@if "%VSINSTALLDIR%"=="" exit /B 1
@exit /B 0
What we can also do, is use the SolutionPackager.exe that comes as part of the Microsoft Dynamics CRM SDK, to unpack the solution.
The great thing about doing this is that we can check this in to TFS and we now have version control on each of our entities, attributes, forms etc. This means that we can if someone makes a change, compare our version to a previous version and see what the difference is.

So we have the batch file, but how do we run it? OK we can run this as part of our build definition so that we get a fresh export each time the build definition of our automated deployment build definition runs. There is also a neat way of using this if we want to run the export manually in that we can create an external tool.
To do this we click on Tools > External Tools, and then add a new tool.
Here we put a title in, and use CMD.EXE as the command we want to run, along with the arguments of:
/c "$(ProjectDir)\ExportSolutions.bat"
And set the initial directory to:
$(ProjectDir)
We also tick the Use Output Window, which means that we can see the output of the solution export when it runs.
We simply click on the Add Command button, select Tools from the Categories, and then find our External Command # from the list, which in my case was External Command 4.
Now we have a button on the menu, which when we click runs the solution export batch file, which exports the solution, and unpacks it.
This will work, however what we need to be mindful of is that using Server Side workspace in TFS 2013 means that the files will be read-only, so we must remember to check out the various folders before clicking the button otherwise we get an access denied error in the output window.
One thing to note, is that if we do check in an unpacked CRM Solution to TFS, including it into a Visual Studio project, Visual Studio recognises the xaml workflows that CRM uses and treats them as WPF xaml files. What we have to do is exclude these from any build actions, simply by right-clicking on the files and set the build action to “None”.
The Deploy Script
We now have our exported solution, we also need to export any settings records from CRM, which we can do in a similar manner to the export solution batch file, and we specify the entities we want to export.
We then have a structure within the Visual Studio project, which contains the utilities, i.e. TSG.MSCrm6.Tools.CommonFunctionsCLI, TSG.MSCrm6.Tools.DataManagerCLI, and TSG.MSCrm6.Tools.SolutionManagerCLI
We have a Logs folder which will contain any import logs files from the CRM Solutions that we do, a Settings folder which contains the output csv files from CRM that we need to re-import into the continuous deployment test environment, and the CRM Solution zip files that we need to import.
There are a couple of other files that we also need, these are the Deploy.bat file, which is the main deployment script, and a SQL script that we can use to restore the CRM Organisation database back to a clean and known state.
Note: We could roll the whole Virtual Environment back to a snapshot, but if we have a number of projects running on the same CRM environment then we can do it at an organisation level and it also saves time in the deployment. We could have also used a PowerShell script, but the idea with the deployment steps was that they are as simple as possible with any complexity being encapsulated in the core libraries so that they can be unit tested if needed.
In our solution we also have a couple of other files which we use in the final manual production deployment, these are the Installer.bat, InstallationSteps.xml and TSG.MSCrm6.Tools.Installer.
So the SQL Script that we can use is as follows:
SELECTSYSTEM_USER
GO
ALTERDATABASE [TST001_MSCRM] SETSINGLE_USERWITHROLLBACKIMMEDIATE
GO
RESTOREDATABASE [MSCRM_CONFIG] FROM DISK=N'D:\MSSQL11.MSSQLSERVER\MSSQL\Backup\RU1\MSCRM_CONFIG.bak'WITH FILE= 1, NOUNLOAD, REPLACE, STATS= 10
GO
RESTOREDATABASE [TST001_MSCRM] FROM DISK=N'D:\MSSQL11.MSSQLSERVER\MSSQL\Backup\RU1\TST001_MSCRM.bak'WITH FILE= 1, NOUNLOAD, REPLACE, STATS= 10
GO
ALTERDATABASE [TST001_MSCRM] SETMULTI_USERWITHROLLBACKIMMEDIATE
GO
With the Deploy.bat being as follows:
ECHO REM set build path
SET buildlocation=%1
ECHO REM set deployment path
SET targetdir="C:\Deploy"
rmdir /s /q %targetdir%
ECHO REM create deployment directory
if not exist %targetdir% (cmd /c mkdir %targetdir%)
ECHO REM copy build to the deployment directory
xcopy /c /y /e %buildlocation%\*.* %targetdir%
SET url=http://localhost:5555/TST001/
SET domain=DEV
SET username=CRM.Admin
SET password=Password
SET ConnectionString="Url=%url%;Domain=%domain%;Username=%username%;Password=%password%"
cd %targetdir%
NET STOP MSCRMAsyncService
NET STOP MSCRMAsyncService$maintenance
NET STOP MSCRMMonitoringService
NET STOP MSCRMSandboxService
NET STOP MSCRMUnzipService
NET STOP MSCRMVssWriterService
IISRESET /STOP
ECHO REM Restore the Test Database
sqlcmd -S localhost -i C:\Deploy\restore_ru1.sql -o C:\Deploy\restore.txt
IISRESET /START
NET START MSCRMAsyncService
NET START MSCRMAsyncService$maintenance
NET START MSCRMMonitoringService
NET START MSCRMSandboxService
NET START MSCRMUnzipService
NET START MSCRMVssWriterService
SET CURDIR=%CD%
SET PATH=%CURDIR%\_SolutionManager;%CURDIR%\_DataManager;%CURDIR%\_CommonFunctions
ECHO Checking CRM Prerequisites
TSG.MSCrm6.Tools.CommonFunctionsCLI.exe /method:CheckUserExists "/arguments:CRM Admin" /crmconnectionstring:%ConnectionString%
IF errorlevel 1 GOTO ERROR_HANDLER
TSG.MSCrm6.Tools.CommonFunctionsCLI.exe /method:CheckOrganisationSetting "/arguments:localeid,2057" /crmconnectionstring:%ConnectionString%
IF errorlevel 1 GOTO ERROR_HANDLER
ECHO Importing Pre Import Settings...
TSG.MSCrm6.Tools.DataManagerCLI.exe /operation:import /type:data /crmconnectionstring:%ConnectionString% /mode:create /datafilepath:%CURDIR%\Settings\001-PreImport\ /datafiletype:csv
IF errorlevel 1 GOTO ERROR_HANDLER
ECHO Import Solutions...
TSG.MSCrm6.Tools.SolutionManagerCLI.exe /operation:import /publishworkflows /crmconnectionstring:%ConnectionString% /solutionfilepath:%CURDIR%\Solutions\ /logfilepath:%CURDIR%\Logs\
IF errorlevel 1 GOTO ERROR_HANDLER
ECHO Importing Settings...
TSG.MSCrm6.Tools.DataManagerCLI.exe /operation:import /type:data /crmconnectionstring:%ConnectionString% /mode:create /datafilepath:%CURDIR%\Settings\002-Main\ /datafiletype:csv
IF errorlevel 1 GOTO ERROR_HANDLER
TSG.MSCrm6.Tools.DataManagerCLI.exe /operation:import /type:data /crmconnectionstring:%ConnectionString% /mode:update /datafilepath:%CURDIR%\Settings\002-Main\ /datafiletype:csv
IF errorlevel 1 GOTO ERROR_HANDLER
EXIT
:ERROR_HANDLER
ECHO An Error has occurred, Installer has been stopped
EXIT /b 1
So, to explain the script… The script is run by the TFS Build / Test Server, and is copied to the Virtual Machine that we have set up as part of our TFS Lab Environment. The first thing that the script does is to copy across all of the files and folders in the Deployment folder, creating a C:\Deploy folder if it doesn’t exist.
We then set up a few variables that we can use to pass to each of the command line executables. We stop each of the Microsoft Dynamics CRM 2013 windows services,
and call the SQL script on the local machine to restore the organisation and config databases. Then we start the Microsoft Dynamics CRM 2013 windows services back up again.

The script then runs a few prerequisite checks and would fail the deployment if e.g. a user called CRM Admin didn’t exist. Note: The reason we do this, is so that any Plugins that are part of the solution and that we wanted to run as a specific user, don’t get registered as calling user if the user doesn’t exist.
We then import any settings e.g. Queues, that might be referenced in the Workflows within the CRM Solution.![]()

The script then imports the CRM Solutions in order by the number prefix, and imports and main setting records that we need, followed by any Many to Many Associations, and any updates to say Mail Merge templates that would be part of the CRM Solution.
Now we can create our TFS build definition in Visual Studio. This is basically just a standard Out of the Box build definition, but we choose to use the LabDefaultTemplate.11.xaml Build process template.
The key thing to note here is that in the Deploy stage, we select the Web Server Role in the machine we want to deploy to and specify the deploy script, passing in the Build Location on our TFS Build / Test Server so we can copy the deployment files across the network.
Now that the build definition has been created you can queue a new build to be run by the TFS Build / Test Server, and you should see a nice green tick once it has completed!
With Microsoft Test Manager, if the coded UI test does fail, you get a fail on the build, but one of the great features that you get is that it takes a screenshot of what went wrong. This means that say someone moved an option from the command bar etc. you can easily be notified on the next build and update the coded UI test accordingly.
So that pretty much wraps up the Microsoft Dynamics CRM 2013 Application Lifetime Management blog series; Of course as with everything Technology related that are many ways achieve the same result, and various different tools that can be used, but hopefully this gives you an idea of how to set up continuous deployment with Microsoft Dynamics CRM 2013, Visual Studio 2013 and TFS 2013, and most importantly I hope you have enjoyed reading this!
Until next time…
@simonjen1