Part 7 – Add our property page to MSI

Place our propertyPage into our MSI

In part 3, we created an MSI, you thought it was complex? This was just the beginning!

As explained before, the complexity resides here in finding the base file location and its modification. Moreover, we will have to handle the cleanup of this file in case of removal.

To achieve this, we will use the “directorysearch” feature of WIX to locate the file then some functions of the Wix utils toolbox to alter the XML file inline.

We already locate the XmlStorage folder with:

<Property Id="XMLSTORAGE" Secure="yes">
     <DirectorySearch Id="CheckSubDir" Path="[UIFOLDER]" Depth="0">
       <DirectorySearch Id="xml_storage_search" Path="XmlStorage" Depth="5" />
     </DirectorySearch>
</Property>

Now, we’ll add the following lines to locate the “extensions/forms” subfolder with the “CollectionResource.xml” file within if it exists.

<Property Id="EXTFORMFLD">
   <DirectorySearchRef Id="xml_storage_search" Parent="CheckSubDir" Path="XmlStorage" >
      <DirectorySearch Id="extensions_fld_search" Path="Extensions" Depth="0" >
         <DirectorySearch Id="extforms_fld_search" Path="Forms" Depth="0" />
      </DirectorySearch>
   </DirectorySearchRef>
</Property>
<Property Id="FRMCOLLRESS">
   <DirectorySearchRef Id="extforms_fld_search" Parent="extensions_fld_search" Path="Forms" >
      <FileSearch Id="frmCollRess" Name="CollectionResource.xml" />
   </DirectorySearchRef>
</Property>

The result will be the creation of 2 properties containing respectively the path and the file name or false if the file is not found.

Let’s do this a second time to keep a reference to the original file.

<Property Id="FORMFLD">
   <DirectorySearchRef Id="xml_storage_search" Parent="CheckSubDir" Path="XmlStorage" >
      <DirectorySearch Id="forms_fld_search" Path="Forms" Depth="0" />
   </DirectorySearchRef>
</Property>
<Property Id="FRMCOLLRESSORIG">
   <DirectorySearchRef Id="forms_fld_search" Parent="xml_storage_search" Path="Forms" >
      <FileSearch Id="frmCollRessOrig" Name="CollectionResource.xml" />
    </DirectorySearchRef>
 </Property>

You may have noticed that we used directorysearch only once for xmlstorage search. All other occurrences are replaced with directorysearchref tags having the same id. This is to reuse the search already done instead of processing it several times for nothing. We use the same behavior when searching for files within folders.

Now, go ahead to the directory tree definition and add references to these folders

<Directory Id="XMLSTORAGE" Name="XMLSTORAGE">
   <Directory Id="EXTENSIONS" Name="Extensions">
……
……
      <Directory Id="FORMS" Name="Forms"/>
   </Directory>
   <Directory Id="FORMSORIG" Name="Forms"/>
</Directory>

As explained earlier, we need to rely on the Wix utils toolbox. So, in the project references add a new reference to the “WixUtilExtension.dll” file usually located in the “bin” subfolder of Wix installation path, then add the declaration of the specific namespace in the “wix” tag at beginning of the WXS file.

Like this:

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension" xmlns:SystemTools="http://schemas.....

Wix Utils are ready to be used!

Add your page project in the “setup” project references to make built files available.

In the components section, we can now add a new component that will set the XML form file.

<ComponentGroup Id="devicecollmembershipcomp" Directory="INSTALLDIR">
   <Component Id="devicecollmembershipdllcomp">
      <File Id="devicecollmembershipdll" Source="$(var.deviceCollMembershipProp.TargetPath)" DiskId="1" KeyPath="yes" />
   </Component>
   <Component Id="formCollRessXml" Guid="GUID_AA" KeyPath="yes">
      <CopyFile Id="formcolressxmlcpy" SourceProperty="FRMCOLLRESSORIG" DestinationProperty="EXTFORMFLD"/>
      <Condition>NOT Installed AND NOT PATCH AND NOT FRMCOLLRESS</Condition>
   </Component>
   <Component Id="colressformregistration" Guid="GUID_BB" KeyPath="yes">
      <util:XmlConfig Id='mypage1delinst' On="install" Action="delete" Node="element" File="[EXTFORMFLD]CollectionResource.xml" ElementPath="/SmsFormData/Form/Pages" VerifyPath="/SmsFormData/Form/Pages/Page[\[]@Id='{GUID-TARGET}'[\]]" Sequence="1001"/>
      <util:XmlConfig Id='mypage1' On="install" Action="create" Node="element" File="[EXTFORMFLD]CollectionResource.xml" ElementPath="//SmsFormData//Form//Pages" Sequence="1002" Name="Page">
         <util:XmlConfig Id="mypage1xmlns" ElementId="mypage1" Name="xmlns" Value="http://schemas.microsoft.com/SystemsManagementServer/2005/03/ConsoleFramework" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1vendor" ElementId="mypage1" Name="VendorId" Value="me" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1id" ElementId="mypage1" Name="Id" Value="{GUID-TARGET}" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1Assembly" ElementId="mypage1" Name="Assembly" Value="$(var.dcmptargetDir)$(var.deviceCollMembershipProp.TargetFileName)" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1Namespace" ElementId="mypage1" Name="Namespace" Value="Microsoft.ConfigurationManagement.AdminConsole.DeviceCollMembership" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1Type" ElementId="mypage1" Name="Type" Value="DeviceCollMembershipPage" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1ReadOnly" ElementId="mypage1" Name="ReadOnly" Value="false" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1MultiSelection" ElementId="mypage1" Name="MultiSelection" Value="false" File="[EXTFORMFLD]CollectionResource.xml"/>
         <util:XmlConfig Id="mypage1Visible" ElementId="mypage1" Name="Visible" Value="false" File="[EXTFORMFLD]CollectionResource.xml"/>
      </util:XmlConfig>
      <util:XmlConfig Id='mypage1del' On="uninstall" Action="delete" Node="element" File="[EXTFORMFLD]CollectionResource.xml" ElementPath="/SmsFormData/Form/Pages" VerifyPath="/SmsFormData/Form/Pages/Page[\[]@Id='{GUID-TARGET}'[\]]" Sequence="1003"/>
   </Component>
</ComponentGroup>

Some people may find this obvious but some will not, so, here is a line by line explanation.

<Component Id="devicecollmembershipdllcomp">
      <File Id="devicecollmembershipdll" Source="$(var.deviceCollMembershipProp.TargetPath)" DiskId="1" KeyPath="yes" />
   </Component>

nothing difficult there, we just add our brand new dll to the MSI.

<Component Id="formCollRessXml" Guid="GUID_AA" KeyPath="yes">
      <CopyFile Id="formcolressxmlcpy" SourceProperty="FRMCOLLRESSORIG" DestinationProperty="EXTFORMFLD"/>
      <Condition>NOT Installed AND NOT PATCH AND NOT FRMCOLLRESS</Condition>
   </Component>

This has to bee explained:
the CollectionResource.xml file has to be copied from xmlstorage\form to the xmlstorage\extensions\forms to be modified. this must only occur once at the first installation time. if the file is already present ( i.e. another 3rd party tools already use it), the target file should be overwritten. That is the purpose of the condition
Not Installed => almost clear: this tool should not be already present on this computer
Not patch => we don’t copy the file if we just update the tool
Not FRMCOLLRES => this is the result of our file search. this will be only positive if the target file has not been found

If these 3 conditions are met, then we can copy the original to the extensions folder. ( don’t forget to place your own generated GUID in the dedicated property.

The last component is nothing more than the inline edition of the XML file.

The first element (id=mypage1delinst) can look like a bit strange as this is a deletion. I have to explain why it exists.
“util:XmlConfig” can be conditionnally used depending on the value of its “ON” property. the problem is that this property only allows two values: install or uninstall.
But how do we manage an update? An update is also a kind of installation so the added line will be duplicated.
This is why I’ve added the same deletion task on installation and uninstallation (last XmlConfig element of the component). With this workaround, I ensure that the line is removed before recreating it. ( no more duplicate )

For people who don’t understand this, ‘ VerifyPath=”/SmsFormData/Form/Pages/Page[\[]@Id='{GUID-TARGET}'[\]]” ‘ is an XPath definition. Look around on the internet and you surely will find interting readings about this way of targeting XML elements.

The next lines define the creation of the new xml element ( surrounding XmlConfig ) with several nested XmlConfig to define all it’s properties.

XmlConfig Id=’mypage1’ On=”install” Action=”create” Node=”element” File=”[EXTFORMFLD]CollectionResource.xml” ElementPath=”//SmsFormData//Form//Pages” Sequence=”1002″ Name=”Page”
->We create a new element during installation the file specified under the SmsFormData\Form\Pages element.

Here is the definition of the first properties. As they’re all constructed the same way, I will explain only this one.

util:XmlConfig Id=”mypage1vendor” ElementId=”mypage1″ Name=”VendorId” Value=”me” File=”[EXTFORMFLD]CollectionResource.xml”

ElementId=”mypage1″ => even if this element is surrounded by the Xmlconfig element that creates the XML object, we must declare a reference to it ( I don’t understand why but this is mandatory. other users over internet complain about this behavior)

Name=”VendorId” => easy ! this is the name of the new property
Value=”me” => and then it’s value ^^

File=”[EXTFORMFLD]CollectionResource.xml” => there again something I don’t understand. Why do we need to redefine the target file when we already have a reference to an existing Xml object in a specific file.

Attention: You may have noticed that the first element redefine the xmlns property. Skilled people may wonder why I did that instead of relying on the default namespace. Answer: This extension will redefine it anyway. if you don’t redefine it, you will end with an xmlns=”” entry making your new element ‘invisible’ for the SCCM console.

That’s all!

The last XmlConfig item should be quite easy to understand now that you already read all this!

Leave a Reply

Your email address will not be published. Required fields are marked *