Since CM2103 there is a new method to deploy console extension. With it, we can avoid creating MSI and manage manual deployments.
There are some pros and cons, let’s review the obvious ones.
pros:
– no need to create an MSI and manage installation globally (which can be enforced to clients)
– update is then also simplified
– can be shared thru the community hub
cons:
– extension should be packed in a signed cab file
Personally, I still didn’t manage to automate cab creation with Visual Studio as I did with the MSI.
Hopefully, since version 2111, with some settings adjustments, the cab file can be used unsigned but it’s clearly not recommended and should be used only for testing purposes.
Here I will not explain how to sign the extension and only focus on how to create the package.
Let’s begin!
The required files are almost the same:
– XML extension files (the ones that were formerly placed in the XmlStorage subfolder)
– all compiled files from your projects ( dlls, config files, etc.)
– a single Manifest.xml file that will describe how the files mentioned above are organized
All the magic resides in the new Manifest.xml file, so let’s dig into it.
The global structure looks as follows:
<CustomExtensionManifest ExtensionID="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" Name="my extension" Description="provide information about many usefull things" Version="1.0" Author="me">
<Deployments>
<ActionExtensionDeployment ParentNode="f10d0965-4b26-4e37-aab5-5400fbbc8eaa">
...
</ActionExtensionDeployment>
<ActionExtensionDeployment ParentNode="018b767a-5a2c-4448-b233-587fceed7c94">
...
</ActionExtensionDeployment>
</Deployments>
</CustomExtensionManifest>
The “CustomExtensionManifest” tag contains the details required by the console itself to present your extension pack:
– ExtensionID: a unique uuid identifying the pack (you can generate it with VS or with one of the many online generators)
– Nane & Description: info that will be shown in the console dedicated pane.
– Version: a mandatory field that helps to manage upgrades
– Author: quite self-explanatory…..
Then comes the inner “Deployments” tag in which will be located every single extension you wish to deploy.
Its children can be ether :
– ActionExtensionDeployment
– NodeExtensionDeployment
– FormExtensionDeployment
– ManagementClassExtensionDeployment
– ViewExtensionDeployment
There is also a “CabExtensionDeployment” child, but let this aside for now.
As you may already have figured out what each of these refers to. Formerly, depending on the type of extension you want to install, you had to choose in which XmlStorage\extensions subfolder to place your XML extension file.

Even if it’s obvious, let’s look at a simple example.
To add an action to the “overview” node of the “Asset and compliance” workspace, you had to create an XML extension file and place it a folder name “f10d0965-4b26-4e37-aab5-5400fbbc8eaa” under “…\XmlStorage\Extensions\Actions”
With the new method, create the exact same extension file and create the following entry in the manifest.xml
<CustomExtensionManifest ExtensionID="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" Name="my extension" Description="provide information about the active many usefull things" Version="1.0" Author="me">
<Deployments>
<ActionExtensionDeployment ParentNode="f10d0965-4b26-4e37-aab5-5400fbbc8eaa">
<FileList>
<File Name="overview.xml">
<Hash Algorithm="sha256">34e09e2eab8eac2992497c1c0b1cabf700eba28fdf8e29e271bd2fb6ada888b3</Hash>
</File>
</FileList>
</ActionExtensionDeployment>
</Deployments>
</CustomExtensionManifest>
What you should notice here is that you are required to calculate the hash of the files you want to mention in this XML:
the easiest way I found is to use the certutil tool as follows:
certutil.exe -hashfile .\filename SHA256
If your extension relies on a custom dll then you’ll have to add another “File” tag in the “FileList” ( also with its own hash value)
Sounds quite easy, isn’t it?
Where things start to be a little bit tricky is when you want to attach the exact same extension to another console node. It seems obvious to just duplicate the “ActionExtensionDeployment” item and change its “ParentNode” property but in fact, this simply doesn’t work at all!
Why? It looks like, when parsing the manifest, the installer engine considers a hash duplicate as a syntax error.
said differently: a “File” hash value should only appear once in the manifest file.
We can easily circumvent this for the XML extension file by creating a specific one for each “extension”
On my side, I just add a comment on the first line of each file to keep track of which node it should assign to so that they all have a different hash.

I also add the 6 first digits of the guid as filename suffix.
“Not a big deal” you would say! true except that when it comes to duplicate dll or script entries….
Fortunately, there is another way to solve this issue:
Place all required files in a cab archive and add them to the Manifest as follow:
<CabExtensionDeployment>
<FileList>
<File Name="ext_content.cab">
<Hash Algorithm="sha256">a50b7278e2d9fce6ca046272fdca3664e4710166cfc6e6a88f5afe3627e90aae</Hash>
</File>
</FileList>
</CabExtensionDeployment>
If we briefly summarize :
– copy all your files into a dedicated folder
– create a cab with all the dependencies ( scripts, dlls, config files,…)
– duplicate XML extension as much as needed to have a dedicated one for each node you want to attach it to.
– calculate the hash value for every single file and build your manifest.xml file
– finally, wrap up all this into a global cab file
Once done, you can add your extension to your infrastructure. (see Console extension registration through community hub – Configuration Manager | Microsoft Docs)
If you don’t know how to create a Cab file, a simple way to achieve this is to use the makecab.exe tool that is provided with your OS.
To do so, create a directive (ddf) file for both content cab and global extension cab.
They should look like this :
.Option Explicit .set DiskDirectoryTemplate=. .Set CabinetNameTemplate="ext01.cab" .Set MaxDiskSize=0 .Set CabinetFileCountThreshold=0 .Set UniqueFiles=OFF .Set Cabinet=ON .Set Compress=ON .Set CompressionType=MSZIP "ext_content.cab" "overview-f10d0965.xml" "overview-018b767a.xml" "manifest.xml"
Then run the makecab.exe command:
makecab /f .\directives.ddf
This will create an ext01.cab file along with a few other files that you can safely ignore.
Note that I didn’t cover how to sign the cab file so to import such extension you will have to allow unsigned cab in oyour config manager config. This can be done in the hierarchy settings under the general tab ( “Hierarchy approved console extension can be unsigned” option)
One more thing I didn’t mentioned above: each xml extension file should be review to correct the dependencies location. With the MSI method, we have control over the path where our dlls, scripts,.. are stored. Now, this is the console that manages this.
Until now, I only created archives with dependency files in the root folder. We can probably include subfolders but it will require some more testing to ensure it works.
So, if you do it like me, don’t forget to remove the leading path to your files.
<actionassembly> <assembly>myLib.dll</assembly>
...
</actionassembly>
Edit:
I finally succeed to organize files with subfolder in the cab archive but it’s a bit tedeous..
In the directive file, you must group file that reside in the same folder and place a directive “DestinationDir” above each group of line as follows:
.Set DestinationDir=ext1
"ext1\myLib.dll"
.Set DestinationDir=ext2
"ext2\mySecondLib.dll"
"ext2\mySeconfLib.cfg"
Then update extension xml file as required like this:
<actionassembly> <assembly>ext1\myLib.dll</assembly>
...
</actionassembly>
<actionassembly> <assembly>ext2\mySecondLib.dll</assembly>
...
</actionassembly>