Using Xsd to Dynamically change DataPort during install

This post will lead to code which can manipulate the list of available source format in Content Manager's DataPort.  The code is executed as the last action within an installer project for a new data provider.  It loads an Xml document representing the user's list of sources and then either adds to the list or updates existing entries.  During uninstall it will remove only the newly installed source.

What I needed to accomplish this:

  • Microsoft Visual Studio 2017
  • .Net Framework 4.5.2
  • WiX Toolset v3
  • HPE Content Manager 9.1

The user's list of available DataPort sources is located off the roaming user profile.  Within it exists one node typed "ArrayofDataFormatterDefinition".  That, in turn, contains one or more DataFormatterDefinition children.  

2017-09-24_6-30-50.png

The goals include: add, update, or remove items from this configuration file.


First I launched the Developer Command Prompt for Visual Studio 2017 so that I could mirror someone else's model within my own project.

2017-09-24_4-51-58.png

I navigated into the roaming application data directory for data port preferences.  Executing XSD within that starting directory will make it easier to organize my results.

2017-09-24_4-56-36.png

We can only use XSD on files ending with an ".xml" extension, which the developers have curiously not done.  Since I also don't want to mess up my own copy some how, I might as well go ahead and copy what I've got to a file XSD will accept.  

I did this by executing

copy ImportDataFormatters ImportDataFormatters.xml

Then I execute

xsd ImportDataFormatters.xml

Command Prompt after generating scheme definition

Command Prompt after generating scheme definition

Next I execute

xsd ImportDataFormatters.xsd /c

Command Prompt after generating class definition

Command Prompt after generating class definition

Next I flipped over to Visual Studio and imported the class file.

2017-09-24_5-44-17.png
2017-09-24_6-13-08.png

The file name doesn't match the generated class names.  It doesn't matter either.  What I really want are the properties of the second class defined.  These are the things I want to change for the user. 

2017-09-24_6-15-42.png

In my custom action for this installer I can now serialize and deserialize using the code below.

private static void SaveImportFormattersPreferenceFile(string preferenceFile, XmlSerializer serializer, ArrayOfDataFormatterDefinition importFormatters)
{
    using (TextWriter writer = new StreamWriter(preferenceFile))
    {
        serializer.Serialize(writer, importFormatters);
        writer.Close();
    }
}
 
private static ArrayOfDataFormatterDefinition LoadImportFormattersPreferenceFile(string preferenceFile, XmlSerializer serializer)
{
    ArrayOfDataFormatterDefinition importFormatters;
    using (StreamReader reader = new StreamReader(preferenceFile))
    {
        importFormatters = (ArrayOfDataFormatterDefinition)serializer.Deserialize(reader);
        reader.Close();
    }
 
    return importFormatters;
}

Next I need the logic to find entries in the list or to create a new one.

XmlSerializer serializer = new XmlSerializer(typeof(ArrayOfDataFormatterDefinition));
ArrayOfDataFormatterDefinition importFormatters = LoadImportFormattersPreferenceFile(preferenceFile, serializer);
List<ArrayOfDataFormatterDefinitionDataFormatterDefinition> items = importFormatters.Items.ToList();
var item = importFormatters.Items.FirstOrDefault(x => x.ClassName.Equals("CMRamble.DataPort.Acme"));
if (item == null)
{
    item = new ArrayOfDataFormatterDefinitionDataFormatterDefinition();
    items.Add(item);
}

After I'm done manipulating the item I have in memory, I need to save the changes to disk.

importFormatters.Items = items.ToArray();
SaveImportFormattersPreferenceFile(preferenceFile, serializer, importFormatters);

During my uninstall action I need to basically repeat the process, but this time just remove anything matching my class name.

XmlSerializer serializer = new XmlSerializer(typeof(ArrayOfDataFormatterDefinition));
ArrayOfDataFormatterDefinition importFormatters = LoadImportFormattersPreferenceFile(preferenceFile, serializer);
List<ArrayOfDataFormatterDefinitionDataFormatterDefinition> items = importFormatters.Items.ToList();
importFormatters.Items = items.Where(x => !x.ClassName.Equals("CMRamble.DataPort.Acme")).ToArray();
SaveImportFormattersPreferenceFile(preferenceFile, serializer, importFormatters);

And that's it!  The above code can be attached to any WiX installer action.