2

I have been trying to find a way to get a Windows Installer component code given a product code. (I'm actually trying to get the component path for an installed product using a shortcut that doesn't contain the component code, but this is a longer story.)

I have come across the WiX DTF (Microsoft.Deployment.WindowsInstaller) assembly and this looks like a nice way to get MSI stuff done. I was very hopeful when I wrote the following code:

Session product = Installer.OpenProduct(productCode);
ComponentInfoCollection components = product.Components;
ComponentInfo component = components.FirstOrDefault();

I hoped I could get the component code from ComponentInfo. Unfortunately I see no way to do this.

Is it possible to get the component code using the WiX DTF classes? (Or any other way come to that...)

Community
  • 1
  • 1
fractor
  • 1,534
  • 2
  • 15
  • 30

2 Answers2

2

I don't understand the desire to get "the component" for "the product" as it's not a 1:1 relationship. That said this could would do what you ask:

const string PRODUCT_CODE = "{EBBD327E-F220-4567-88F8-CEE3BE560F81}";
var comps = from c in ComponentInstallation.AllComponents
            where c.Product.ProductCode == PRODUCT_CODE
            select c;
string componentId = comps.FirstOrDefault().ComponentCode;
Christopher Painter
  • 54,556
  • 6
  • 63
  • 100
  • I am making the assumption that there is only one component, because no component id is returned from the shortcut I am analysing (see http://stackoverflow.com/q/34638433/3051702). I would rather not enumerate all components if I can avoid it as it is relatively time consuming. I have posted an answer which looks at the database instead. I don't know yet what happens if there are multiple rows in the Component table though, but in my case I don't think this is a concern. – fractor Jan 08 '16 at 16:53
  • I think the comment I left in the other thread will be helpful. You should be able to traverse the tables to get from the icon id back to the keypath of the component to find out what the shortcut owner is. It's just odd to me that the MSI API isn't a little better. There might be another way so I'd reach out to the people I mentioned. – Christopher Painter Jan 08 '16 at 16:59
  • But I think you're saying there's not necessarily a single component path corresponding the the icon because multiple components can use the same icon. – fractor Jan 11 '16 at 09:04
  • Testing has shown that the assumption of a component id not being returned only when there is a single component is not correct. It is possible to have no component id returned from MsiGetShortcutTarget even when there are multiple components. – fractor Jan 11 '16 at 14:35
0

I have found it is possible to get the component code using the following:

private string GetComponentIdFromMsi()
{
    using (Session product = Installer.OpenProduct(_productCode))
    using (Database database = product.Database)
    {
        var featureComponentsRecords = GetFeatureComponents(database);
        var featureComponentsRecord = featureComponentsRecords.Single(x => x.Feature == _feature);
        var componentRecords = GetComponents(database);
        var componentId = componentRecords.Single(x => x.Component == featureComponentsRecord.Component).ComponentId;
        return componentId;
    }
}

private IEnumerable<FeatureComponentsRecord> GetFeatureComponents(Database database)
{
    var list = database.ExecuteQuery("SELECT `Feature_`, `Component_` FROM `FeatureComponents`");
    const int columnCount = 2;
    const int featureOffset = 0;
    const int componentOffset = 1;
    int rowCount = list.Count / columnCount;

    for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
    {
        var rowOffset = rowIndex * columnCount;
        yield return new FeatureComponentsRecord((string)list[rowOffset + featureOffset], (string)list[rowOffset + componentOffset]);
    }
}

private IEnumerable<ComponentRecord> GetComponents(Database database)
{
    var list = database.ExecuteQuery("SELECT `Component`, `ComponentId` FROM `Component`");
    const int columnCount = 2;
    const int componentOffset = 0;
    const int componentIdOffset = 1;
    int rowCount = list.Count / columnCount;

    for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
    {
        var rowOffset = rowIndex * columnCount;
        yield return new ComponentRecord((string)list[rowOffset + componentOffset], (string)list[rowOffset + componentIdOffset]);
    }
}

private class FeatureComponentsRecord
{
    public FeatureComponentsRecord(string feature, string component)
    {
        Feature = feature;
        Component = component;
    }

    public string Feature { get; private set; }
    public string Component { get; private set; }
}

private class ComponentRecord
{
    public ComponentRecord(string component, string componentId)
    {
        Component = component;
        ComponentId = componentId;
    }

    public string Component { get; private set; }
    public string ComponentId { get; private set; }
}
fractor
  • 1,534
  • 2
  • 15
  • 30