Google Analytics

Showing posts with label Wix. Show all posts
Showing posts with label Wix. Show all posts

Monday, January 19, 2015

Creating a Local Windows Group on Install

One of the recent Installer/Wix issues that needed to be addressed in our product was to automatically create a Windows group on install. Our installer creates and starts a number of Windows services that are run as a particular user with the appropriate permissions. Currently, whomever is installing the product must create the local group, create the user account to run the services and add the user to the local group before running our installer. In order to count down on the tasks the user must do manually, we added a story to both create the local group and user on install, and I first took to doing a spike solution to create the group.

Doing a spike allowed me to isolate the action needed to just creating the group. Our installer is currently made up of several Wix libraries, approximately a dozen Wix source files, and a number of custom actions. I wasn't confident about finding just the right place to add the group before I even understood how to do it. Also, the spike allowed me to quickly run the install and uninstall without going through all the other actions our current installer does.

My hope was that I could do it fairly simply with Wix or with the Wix Utilities extension. The utilities extension does provide a group element; however, "This element is not capable of creating new groups but can be used to add new or existing users to an existing group." Luckily a Google search indicated that someone had already done the work to create the necessary custom actions to do this. Msiext (http://dblock.github.io/msiext/) contains a UserPrivileges extension along with a number of other Wix extensions which I wish I had known about before. The download includes runnable demos, but there isn't much in the way of documentation. Ergo, I'm writing this blog post rather than simply pointing you to some documentation there.

So I got the latest msiext download. I created a console app as my spike and added a Wix Setup project to the solution. To the setup project I added a reference to the WixUserPrivilegesExtension DLL in the msiext package. This gives access to the LocalGroup element:

<UserPrivileges:LocalGroup Id="DemoLocalGroup1" Name="DemoLocalGroup1"
Description="Demo local group 1 for a spike." CheckIfExists="yes"
CreateOnInstall="yes" DeleteOnUnInstall="yes">
</UserPrivileges:LocalGroup>

The explanation is pretty straight-forward. the Id is the unique element id, Name is the name of the group and description is what appears in the description when you Manage Groups in Windows. CheckIfExists will prevent it from re-creating the group if the group already exists. CreateOnInstall creates the group on install and DeleteOnUnInstall will delete the group on uninstall. These can be changed if, for example your application creates the groups itself and all you want to do is delete it on uninstall, or if you want to leave the group unchanged even if the user chooses to uninstall. Like I said, pretty straight-forward.

My entire Product.wxs looks like so:


xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:UserPrivileges="http://schemas.appsecinc.com/wix/UserPrivilegesExtension">
    <Product Id="*" Name="SetupProject1" Language="1033" Version="1.0.0.0" Manufacturer="PlutoTech" UpgradeCode="5eec8bc9-ee30-451b-b3c2-fd4ce78bf277">
        <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
        <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
        <MediaTemplate />
        <Feature Id="ProductFeature" Title="SetupProject1" Level="1">
            <ComponentGroupRef Id="ProductComponents" />
        </Feature>
    </Product>

    <Fragment>
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLFOLDER" Name="SetupProject1" />
            </Directory>
        </Directory>
    </Fragment>
 
    <Fragment>
        <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
            <Component Id="LocalUserGroupDemo1" Guid="YOUR-GUID-HERE">
                <UserPrivileges:LocalGroup Id="DemoLocalGroup1" Name="DemoLocalGroup1"
                    Description="Demo local group 1 for a spike." CheckIfExists="yes"
                    CreateOnInstall="yes" DeleteOnUnInstall="yes">
                </UserPrivileges:LocalGroup>
                <File Id="File.MyService" KeyPath="yes" Source="$(var.AddLocalGroupSpike.TargetPath)" />
            </Component>
        </ComponentGroup>
    </Fragment>
</Wix>

Note that the INSTALLFOLDER cannot be the keypath for the component. In this case it is actually the executable, Id="File.MyService. In our actual installer, the component that creates the necessary groups is separate from any other files, but the component group it belongs to is associated with the TARGETDIR directory.

Monday, June 2, 2014

Wix and Feature Install Levels

I was just doing some digging for a colleague to find information on Wix and installing separate features by default or within a typical or custom install. This article does a comprehensive job of covering the topic. So for my own future reference and rather than rehash it, I will just post it here. (http://blogs.technet.com/b/alexshev/archive/2008/08/23/from-msi-to-wix-part-4-features-and-components.aspx). Note: I haven't actually tried the sample code yet.

Wednesday, March 12, 2014

WiX Questions

If you've posted some question about WiX to me on here, I haven't forgotten about you. I just haven't made it a priority to try and find the answer for you. :) I post here because I am discovering how to use some of these things just as many of you are, and admittedly there isn't a wealth of information out there.

Seriously, though, I appreciate the questions and want to take some time, even out of my own curiosity, to find the answers. Between work and life and trying to get ready for the fantasy baseball season, and flossing my gerbil's teeth, however, there just hasn't been much time. If you've posted questions and subsequently found the answers out there in the Tubes somewhere, I'd appreciate if you could post your answer here. "Make this your action item." Thanks!

.Net Reactor and WiX

Okay, here's the situation: ("My parents went away on a week's vacation...") I need to obfuscate the code of a project I am working on using .Net Reactor. The project is in .Net (obviously) and we're using VS 2012 and WiX 3.7 to create the installer. Obfuscating code with .Net Reactor is relatively simple as well as powerful. In a side-by-side comparison with dotfuscator we found that Reactor did a better job particularly in control flow obfuscation than its more expensive counterpart. The issue before me is getting WiX to do this obfuscation before creating the installer.

Actually the project is broken up into several solutions, a client and several services that get packaged together. Obfuscating the initial project, the client, was relatively straightforward. First I created a .Net Reactor project specifying the protection options, settings, and files I wanted to obfuscate. I added that project file to my solution as a resource to copy into the output directory. The solution builds a number of projects including some shared libraries, so it was just easiest to copy the project into the output directory and run it from there. I edited the wixproj file (in VisualStudio you can right-click the project file and select 'Edit Project File') and overwrote the BeforeBuild target as such:


 

The .Net Reactor command-line help here, http://www.eziriz.com/help/source/command_line.htm, is useful, though if you are using the project file as I am, most of the settings will come from it. I added the -q[uiet] flag to keep Reactor's notification window from popping up. I should note here that by default a Reactor project will have \_secured\ as the Target File setting. That setting results in each obfuscated assembly being tucked away neatly in its own directory, which I would then have to pick through to pack back into the installer. To remedy this, I just had the obfuscated files overwrite the originals by removing the subdirectory of the Target File:
\. That has worked pretty well for me except that I need to be sure to rebuild the VS solution before building the WiX installer each time, else Reactor finds the previously obfuscated file and chokes.

For each of the services then I built a Reactor project and added multiple elements to the BeforeBuild of the wixproj that builds that installer. I also had to separately build a few other DLLs where I needed to have a lower level of obfuscation. It was simple just to create their own separate Reactor project and execute them in the BeforeBuild as well.

Tuesday, August 27, 2013

Fusion Log Viewer

Back to basics, indeed. The Fusion log viewer is an arrow that should be in the quiver of every Windows application developer. It wasn't in mine, however, until this morning. In our morning Scrum I complained that I was having trouble tracking down an issue with Infragistics Reporting. The report viewer was trying to load some libraries at runtime and was giving me the classic non-descript error: 

"System.NullReferenceException: Object reference not set to an instance of an object." 

The full error is:

Infragistics.Controls.Reports.ReportViewerException: An unknown error occurred while processing the report. ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at Infragistics.Reports.Engine.ServiceModelMefDiscoverer.Initialize(IServiceModelManager serviceModel)
   at Infragistics.Reports.Engine.ServiceModelBuilder.Setup(IServiceModelConfiguration configuration)
   at Infragistics.Controls.Reports.ReportProcessorLocalAdapter..ctor(ReportSource reportSource, Assembly reportAssembly, IEnumerable`1 externalDataSources)
   at Infragistics.Controls.Reports.RenderingEngineFactory.CreateLocalEngine(ReportSource reportSource, Assembly reportAssembly, IEnumerable`1 externalDataSources)
   at Infragistics.Controls.Reports.ProcessingSessionStateMachine.CreateProcessor(IRenderingEngineFactory renderingEngineFactory, IEnumerable`1 externalDataSources, Report reportDefinition, Assembly reportAssembly)
   --- End of inner exception stack trace ---
   at Infragistics.Controls.Reports.ProcessingSessionStateMachine.CreateProcessor(IRenderingEngineFactory renderingEngineFactory, IEnumerable`1 externalDataSources, Report reportDefinition, Assembly reportAssembly)

   at Infragistics.Controls.Reports.ViewerStateMachine.RenderHelper(RenderParameters renderParameters, Report reportDefinition, Assembly reportAssembly)

We narrowed it down to an issue loading libraries, since the viewer was working on my machine where I had the Infragistics tools installed.

My colleague said, "No problem. We'll just look at it in the Fusion log and see what the issue is." I gave him a blank stare. "Find me in a bit, and I'll show you." Not to throw said colleague under a bus, but then he spent the next 90 minutes on the phone. Important stuff, but I have these nice reports that I want to show off to the team.

Then I found this blog from Scott Hanselman (who has saved me on a number of such occasions): http://www.hanselman.com/blog/BackToBasicsUsingFusionLogViewerToDebugObscureLoaderErrors.aspx. It is very simple to fire up and use, and I am kicking myself for the times that I had similar issues that took me hours if not days to track down.

After firing up the Fusion Log Viewer on a machine where I did not have Infragistics installed, clearing the logs, and then running the troubled application, I found that the app was trying to load up two libraries (InfragisticsWPF4.Reports.Controls.Charts.XamDataChart.v13.1.dll and InfragisticsWPF4.Reports.Controls.DataVisualization.v13.1.dll for those of you who may run into the same issue) that I had added the references to but had forgotten to include in the Wix installer. With the libraries added I can finally show off what has been almost 3 weeks of work on the reporting functionality of this application.

Friday, March 1, 2013

More Custom Actions with WiX


The tutorial gave a relatively simple action to check a product key on install. For my own project I would like to ask the user to specify a remote service uri, check that the uri format is okay, warn the user if it is not, and finally write the value to my application’s configuration file.

First of all, my installer had no user interface at all, so I added the WixUI_FeatureTree as I described in a previous post. As in my tutorial, I am going to add the new custom action code by right-clicking the solution and doing Add > New Project and selecting Windows Installer XML > C# Custom Action Project and I will name it CustomInstallActions.

 The custom action will read in what the user input as SERVICEURI and will output a URIACCEPTED value to the install session.  For right now I am simply testing whether the string that is passed in can be used to form a URI.  The CustomAction attribute decorating the class indicates the method is an entry point for the installer custom action.

public class CheckUriAction
    {
        /// 
        ///     Checks that user input is a well-formed URI.
        /// 
        /// 
        ///     The installation session.
        /// 
        /// 
        ///     The . This will always be success so
        ///     long as the evaluation can be made. It does not indicate
        ///     whether the URI is well-formed or not.
        /// 
        [CustomAction]
        public static ActionResult CheckUri(Session session)
        {
            // log that custom action is being called.
            session.Log("Checking user input URI");
            
            // get the user string input from the install session
            var uri = session["SERVICEURI"];
            
            // check that the string passed in can be used to construct a valid URI
            var isURI = Uri.IsWellFormedUriString(uri, UriKind.Absolute);
            
            // write the evaluation back to the installation session
            session["URIACCEPTED"] = isURI ? "1" : "0";
            
            // return succes. This allows the installation to continue and the
            // user will be able to change the input if necessary. A different
            // ActionResult would cause the installation to end.
            return ActionResult.Success;
        }

To my Product.wxs WiX file I add a Fragment to reference this custom action and the dll that contains it. As I explained in a previous post, the C# custom action gets wrapped by [someEXE] into a managed custom action. This is handled automatically if you use the WiX C# CustomAction project, so the actual dll we reference is the wrapped one that has the .CA.dll extension.

<Fragment>
    <CustomAction Id='CheckingUri' BinaryKey='CheckUri.CA' DllEntry='CheckUri' />
    <Binary Id='CheckUri.CA' SourceFile='..\..\..\CheckURIAction\bin\Debug\CustomInstallActions.CA.dll' />
</Fragment>

The DllEntry attribute of the CustomAction is the name of the method that we designated as the entry point in our class. The Id of the CustomAction element is what we will use to reference this in our installation UI.

This UI is a new dialog I add to the WiX project that will ask for the user’s input.  By right-clicking on the WiX project and selecting Add > New Item > WiX > WiX File I’m given the skeleton of a WiX fragment. To this I add a UI component and within that a Dialog I will call ServiceDlg. If you followed the WiX tutorial you’ve done this. The key controls to look at are the “NameEdit” Edit control and its property called SERVICEURL and the “Next” PushButton control. When the user pushes the Next button it will “Publish” the CheckingUri action we specified in the fragment above. The value of SERVICEURL will be passed through the session to the action. In turn the action adds the URIACCEPTED property to the session, which we evealute to determine which dialog to show to the user. In the case there the URI is not accepted, we show the InvalidURIDlg that we will add below. If everything is ok we allow the user to move on to the CustomizeDlg dialog which is already built in to the WixUI_FeatureTree set.

<Dialog Id="ServiceDlg" Width="370" Height="270" Title="[ProductName] Setup" NoMinimize="yes">
        <Control Id="ServiceLabel" Type="Text" X="45" Y="73" Width="100" Height="15" TabSkip="no" Text="&EED Service Address:" />
        <Control Id="NameEdit" Type="Edit" X="45" Y="85" Width="220" Height="18" Property="SERVICEURI" Text="{80}" />
        <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back">
          <Publish Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
        </Control>
        <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
          <Publish Event="DoAction" Value="CheckingUri">1</Publish>
          <Publish Event="SpawnDialog" Value="InvalidURIDlg">URIACCEPTED = "0"</Publish>
          <Publish Event="NewDialog" Value="CustomizeDlg">URIACCEPTED = "1"</Publish>
        </Control>
        <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
          <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
        </Control>
        <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="WixUI_Bmp_Banner" />
        <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>[ProductName] needs access to the DVS Core Service</Text>
        </Control>
        <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
        <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes">
          <Text>{\WixUI_Font_Title}Core Service Configuration</Text>
        </Control>
        <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
      </Dialog>

I add the InvalidURIDlg to this same UI element in my ServiceDlg.wxs since they go hand-in-hand. This dialog is created from the SpawnDialog event in the ServiceDlg dialog, so it has that dialog as a parent. The dialog displays a message to the user and then the user’s one option is to click a button to make the dialog go away (and return back to the ServiceDlg).

<Dialog Id="InvalidURIDlg" Width="260" Height="85" Title="[ProductName] [Setup]" NoMinimize="yes">
  <Control Id="Return" Type="PushButton" X="100" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Return">
    <Publish Event="EndDialog" Value="Return">1</Publish>
  </Control>
  <Control Id="Text" Type="Text" X="48" Y="15" Width="194" Height="30" TabSkip="no">
    <Text>The service location provided is not a valid URI.</Text>
  </Control>
</Dialog>
 
The final thing I need to do show this dialog is fit this new dialog into the install sequence.  I add the dialog reference to the same Fragment where I reference the FeatureTree set. Then I add the events to insert the ServiceDlg between the LicenseAgreementDlg and CustomizeDlg of the FeatureTree set.

 <Fragment>
    <UI Id="MyWixUI_FeatureTree">
      <UIRef Id="WixUI_FeatureTree" />
      <UIRef Id="WixUI_ErrorProgressText" />
 
      <DialogRef Id="ServiceDlg" />
 
      <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="ServiceDlg" Order="2">LicenseAccepted = "1"</Publish>
      <Publish Dialog="CustomizeDlg" Control="Back" Event="NewDialog" Value="ServiceDlg" Order="2">1</Publish>
    </UI>
  </Fragment>

You can run the install as it is and see the custom action “in action” but at this point it isn’t particularly useful unless we do something with the value that the user input. My goal is to write this value to the App.config of my application as the address for a service endpoint. Initially I just want to try to write the name to the appSettings element of the App.config. This is really quite simple with WiX using the XmlFile utility extension.

First we need a reference from the WiX project to the WixUtilExtension library. With that reference in place WiX will handle the correct commands to compile the library in with our installer. So to our main Wix root element we need to add the XML namespace for util.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
 
Then to the same component that contains the configuration file that we want to change we add a series of XmlFile elements. The first will create a new node “add” to the appSettings node in our config file. Note that the configuration must have an appSettings node to begin with, AND even if the node is empty it must have separate opening and close tags. will not work; you need to have under the configuration element. The second element will add a “key” attribute to the “add” element and give it a value of “ServiceUri”. The final element will give a “value” attribute to our element with the value of the SERVICEURI that was passed in from the user. The INSTALLFOLDER in the file references the directory I gave the Id of INSTALLFOLDER which is the main install folder for the application.

         <File Id="File.ElectionDataService.Config" Name="ElectionService.exe.config" Source="$(var.ServicePath)\ElectionService.exe.config"/>
        <util:XmlFile Id='Settings.ElectionService.Config1' File='[INSTALLFOLDER]ElectionService.exe.config'                      
         Action='createElement' Name='add' ElementPath='//configuration/appSettings' Sequence='1' />
        <util:XmlFile Id='Settings.ElectionService.Config2' File='[INSTALLFOLDER]ElectionService.exe.config'
         Action='setValue' Name='key' Value='ServiceUri' ElementPath='//configuration/appSettings/add' Sequence='2' />
        <util:XmlFile Id='Settings.ElectionService.Config3' File='[INSTALLFOLDER]ElectionService.exe.config'
         Action='setValue' Name='value' Value='[SERVICEURI]' ElementPath='//configuration/appSettings/add' Sequence='3' />
 
Running the installation now results in the following appSettings block in the configuration file:
   

Simply writing to the appSettings is okay, but what I really want to do is save the user input in the configuration as the endpoint for a service that the application needs. To do this I need to refine where the XmlFile elements write to. My config file already has the endpoint element that I need; I just want to fill in the address attribute. Before installation it looks like this:
<endpoint binding="wsHttpBinding" bindingConfiguration="wsHttpBindingNoSec"
    contract="ICoordinateInterface" name="WSHttpBinding_ICoordinateInterface">
 
I really only need a single XmlFile element in my Wix file that will add the address attribute to that endpoint node with a value of whatever the user entered. Another consideration is that I actually have multiple endpoints for different services in the configuration file, so I need to select the endpoint with the service interface that corresponds to the one I want to change. I replace the sequence of XmlFile elements above with a new one.

         <File Id="File.ElectionDataService.Config" Name="DVS.EMS.Services.ElectionData.ElectionDataService.exe.config" Source="$(var.ServicePath)\DVS.EMS.Services.ElectionData.ElectionDataService.exe.config"/>
        <util:XmlFile Id='Settings.ElectionDataService.Config' File='[INSTALLFOLDER]DVS.EMS.Services.ElectionData.ElectionDataService.exe.config' 
          Action='setValue' Name='address' Value='[SERVICEURI]' ElementPath='//configuration/system.serviceModel/client/endpoint[\[]@contract="ICoordinateInterface"[\]]' Sequence='1' />

The bracketed @contract=… attribute in the ElementPath picks out the individual endpoint element with the service interface that I want to change, but not the others. The brackets need to be escaped with the pattern shown.

Now running the install my configuration ends up with the following endpoint: