Scripted Solution Exports with Adoxio.Dynamics.DevOps

The previous post looked at the mechanics of adding Adoxio.Dynamics.DevOps scripts to a project. This blog post examines the solution export process.

The Goal

Dynamics 365 solutions are normally managed as zip files which contain all the components of a solution, with the components stored in multiple larger files inside the zip file. The preferred end state is to instead store the solutions in source control with each component in a separate file. As individual files, they are easy to view in source code editing tools like Visual Studio and manage in source control systems like Git.

Configuration Steps

The Adoxio.Dynamics.DevOps module has functions and sample scripts for exporting solutions and preparing them so that they can be stored in source control. The steps to prepare the solutions for export are:

  1. Define the connection to a Dynamics 365 instance
  2. Define the list of solutions to export to local zip files
  3. Define the local solution zip files to extract and unpack

Connection Parameters

The connection to a Dynamics 365 instance is defined in a file in the CrmConnectionParameters folder. The example Online.ps1 file contains the configuration settings for connecting to an online instance:

@{
    OrganizationName = "orgfab51ca3" # see Settings > Customizations > Developer Resources > "Unique Name"
    ServerUrl = "https://fabrikam.crm.dynamics.com" # the online organization URL
    Credential = (Get-Credential) # prompt for credentials
}

Refer to the other sample files for example configurations for IFD and on-premise instances. Create or update new files in this folder to define the instances that exports will be performed from.

Solution Export Settings File

The solutions to export and unpack are defined in a file inside the ExportSettings folder. The example Full.ps1 file contains configuration settings for two solutions that should be exported and unpacked. Create one or more settings file for each combination of unmanaged solutions that will be developed in a single Dynamics 365 instance.

The remaining sections describe the contents of a settings file inside the ExportSettings folder.

Solutions to Export

The ExportSolutionsobject’s Solutions array defines the solutions to export. Each solution is listed twice with varying parameters, so that the unmanaged and managed versions of the solutions will be exported.
The example Full.ps1 file contains configuration settings for two solutions that will be exported:

ExportSolutions = [PSCustomObject]@{
    CrmConnectionParameters = $CrmConnectionParameters
    Solutions = @(
        [PSCustomObject]@{
            SolutionName = 'AdventureWorks'
            Managed = $false
            ZipFile = "$projectRoot\temp\export\AdventureWorks.zip"
        },
        [PSCustomObject]@{
            SolutionName = 'AdventureWorks'
            Managed = $true
            ZipFile = "$projectRoot\temp\export\AdventureWorks_managed.zip"
        },
        [PSCustomObject]@{
            SolutionName = 'Contoso'
            Managed = $false
            ZipFile = "$projectRoot\temp\export\Contoso.zip"
        },
        [PSCustomObject]@{
            SolutionName = 'Contoso'
            Managed = $true
            ZipFile = "$projectRoot\temp\export\Contoso_managed.zip"
        }
    )
}

SolutionNamespecifies the unique name of the solution to export.

ZipFile specifies the location on the file system to export the solution to. The file names must follow a very specific naming format – for unmanaged solutions use the solution name, and for managed solutions, use the solution name appended by _managed. This rule for naming is dictated by the SolutionPackager tool in the Dynamics 365 SDK. The exported solution zip files are saved to a temporary export folder so that unpacking operations can be performed on the solutions prior to committing them to source control.

Documentation for the ExportSolutions parameters is also available by looking at the Export-CrmSolutions help:

Get-Help -Name Export-CrmSolutions -Full

Solutions to Extract

The solution zip files to extract are defined in the ExportSolutionsobject.
The example Full.ps1 file contains configuration settings for two solutions that will be extracted:

ExtractSolutions = @(
    [PSCustomObject]@{
        ZipFile = "$projectRoot\temp\export\AdventureWorks.zip"
        MappingXmlFile = "$projectRoot\crm\solutions\AdventureWorks.mappings.xml"
        PackageType = 'Both' # Unmanaged, Managed, Both
        Folder = "$projectRoot\crm\solutions\AdventureWorks"
    },
    [PSCustomObject]@{
        ZipFile = "$projectRoot\temp\export\Contoso.zip"
        MappingXmlFile = "$projectRoot\crm\solutions\Contoso.mappings.xml"
        PackageType = 'Both' # Unmanaged, Managed, Both
        Folder = "$projectRoot\crm\solutions\Contoso"
    }
)

ZipFilerefers to the zip file on the file system that is going to be unpacked. It should be the file path of the unmanaged solution file that was previously exported. Even though only the unmanaged filename is specified, the SolutionPackager will read the managed version as well because PackageTypeis set to Both.

MappingXmlFile specifies an XML file used by the SolutionPackager tool to control which web resources and DLL files in a solution are not retained after an unpack operation so that duplicate files are not stored in your source control system. For example if you had implemented a CSS file that was stored in a Visual Studio project, and then uploaded it as a web resource to Dynamics 365, it’s a good candidate to exclude after export.

Here is an example XML file to map static web resource files in a solution to a folder containing the original source files, and to map an individual DLL file from a plug-in to the bin folder of a class library project. This configuration requires the URL structure of web resources to match the file system hierarchy:

<?xml version="1.0" encoding="utf-8"?>
<Mapping>
   <Folder map="WebResources\adoxio_" to="..\..\src\MyProject.WebResources\adoxio_" />
   <FileToFile map="PluginAssemblies\**\Adoxio.Plugins.dll" to="..\..\src\Adoxio.Plugins\Adoxio.Plugins\bin\**\Adoxio.Plugins.dll" />
</Mapping>

This is one of the more complicated configurations to get working correctly. If you do not want to create a mapping file you can comment out the MappingXmlFile property or delete it so that the mapping logic will not get executed. Refer to the SolutionPackager /map argument for additional details.

PackageType specifies the type of solution to unpack. This setting should be set to the value Both so that the SolutionPackager understands unmanaged and managed versions of the solution exist and the components for both versions should be be retained when the files are unpacked and saved to the file system. Refer to the SolutionPackager documentation for additional details.

Folder specifies the file path to save the extracted (unpacked) version of the solution. In the sample, each solution will reside under a folder in the crm\solutions beneath the root folder of your project. This is the final output of the exports to commit to source control.

Documentation for the ExtractSolutions parameters is also available by looking at the Expand-CrmSolution help:

Get-Help -Name Expand-CrmSolution -Full

Executing an Export

At this point it is possible to perform scripted solution exports using the following command (using the sample parameter values):

./Export.ps1 -CrmConnectionName Online -ExportSettings Full

To be continued

The rest of the settings file contains optional data management settings which I will cover in a future blog post.