1

VBS works as I desired, but both COM API and DTF using C# is not locating the InstallLocation. Followings are what I have done so far.


Thanks to this post, I was able to find a InstallLocation that is not available on registry using vbs. I understand that vbs is calling for COM API available on %WINDIR%\system32\msi.dll.


C# COM API

So I thought I would use C# to call this method up. But it failed. Even though I can confirm the existence and installation, it cannot open one of the product GUID (I tripple checked).

Note: there were products that did not throw exception and InstallLocation were properly found. Its just not all.

Followings are my code.

        static Dictionary<string, string> FindInstallLocationsCOM(Dictionary<string, string> products)
        {
            var locationDictionary = new Dictionary<string, string>();

            // Get the type of the Windows Installer object
            Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");

            // Create the Windows Installer object
            Object installerObj = Activator.CreateInstance(installerType);
            Installer installer = installerObj as Installer;

            foreach (var product in products)
            {
                try
                {
                    var session = installer.OpenProduct(product.Value);
                    if (session != null)
                    {
                        session.DoAction("CostInitialize");
                        session.DoAction("CostFinalize");
                        var installLocation = session.Property["INSTALLLOCATION"];
                        MessageBox.Show(product.Key + "\n" + "Product Code : " + product.Value + "\n" + "Install Location : " + installLocation);
                        locationDictionary.Add(product.Key, installLocation);
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show("Error : Could not open Product " + e.Message + "\n" + "Product : " + product.Key + "\n" + "Product Code : " + product.Value);
                }
            }

            return locationDictionary;
        }

OK that did not work, let's try DTF.


C# DTF

But that also was not successful. Following is my code. This does not trigger exception, and even the one that was not detectable via COM API was able to detect itself, but InstallLocation property was empty string.

Note: there were products that did have InstallLocation property filled. Its just not all.

        static Dictionary<string,string> FindInstallLocation(Dictionary<string,string> products)
        {
            var locationDictionary = new Dictionary<string, string>();

            foreach (var product in products)
            {
                try
                {
                    var installed = new ProductInstallation(product.Value);
                    if (installed != null)
                    {
                        var installLocation = installed.InstallLocation;
                        MessageBox.Show(product.Key + "\n" + "Product Code : " + product.Value + "\n" + "Install Location : " + installLocation);
                        locationDictionary.Add(product.Key, installLocation);
                    }
                    else
                    {
                        MessageBox.Show(product.Key + "\n" + "Product Code : " + product.Value + "\n" + "Is not installed");
                    }
                }
                catch (Exception e)
                {
                    MessageBox.Show("Error :  " + e.Message + "\n" + "Product : " + product.Key + "\n" + "Product Code : " + product.Value);
                }
            }

            return locationDictionary;
        }

Why is VBS able to detect the InstallLocation when neither of C# is not able to? What am I missing?

The reason I cannot use VBS is because the try catch is not available unless I use vb.net.

Shintaro Takechi
  • 1,215
  • 1
  • 17
  • 39
  • The C# code is wrong, you `foreach` is for `Products` but you pass in `products` as the argument to the method. Just saying "OK that did not work, let's try DTF." isn't helpful, did it error, if it did what was it, if it didn't, ask yourself why? – user692942 Jul 29 '20 at 22:55
  • ah my bad. I have Dictionary Products available, but for limitation of scope for posting, I passed in products as an variable. In the process miss match occurred. It is not the actual cause. Thanks for pointing it out though. now edited. – Shintaro Takechi Jul 29 '20 at 22:58
  • 1
    The linked answer used `INSTALLFOLDER` not `INSTALLLOCATION`. Which property did your VBScript check? – user692942 Jul 29 '20 at 23:03
  • `INSTALLLOCATION` – Shintaro Takechi Jul 29 '20 at 23:04
  • 3
    You are not in a custom action I presume? That VBScript actually resolves the directory table and gets the installation location that way. Another way to retrieve the installation directory is to write it during original installation to the registry yourself and then read it back. Would that work for you? Depends what you need and what you are doing. I will look at this when I get back later - essentially you may need to run costing to ensure the directories are resolved - I am not sure what DTF does here auto-magically. You may need to spin up an MSI API session object. – Stein Åsmul Jul 30 '20 at 14:33
  • No I am not in custom action. I would like to access directory table because the GUID from a product I am accessing does not have InstallLocation filled in registry. DTF may not start the costing action, but my first example listed has the COM API from msi.dll route which does the CostInitialize and CostFinalize so I was hoping that it would have the same effect as VBS. – Shintaro Takechi Jul 30 '20 at 15:01
  • @SteinAsmul thanks to your comment, it lead me to the answer. Thanks again. – Shintaro Takechi Jul 30 '20 at 15:54

2 Answers2

2

After SteinAsmul's suggesion that DTF does not automatically call cost related action, I did further reading in the DTF document.

I found an DoAction is also available in DTF. So I used the following, and the var installLocation now has the expected value I was looking for.

Installer.SetInternalUI(InstallUIOptions.Silent);
var session = Installer.OpenProduct(product.Value);
session.DoAction("CostInitialize");
session.DoAction("CostFinalize");
var installLocation = session["INSTALLLOCATION"];
session.Close();
Shintaro Takechi
  • 1,215
  • 1
  • 17
  • 39
  • 1
    Yes DoAction should work for this purpose. Great that it works. You should be able to resolve any directory property like that. – Stein Åsmul Jul 30 '20 at 17:51
  • Still makes me wonder why COM msi route did not work as intended. that one used the costs DoActions as well. – Shintaro Takechi Jul 30 '20 at 17:52
  • I tried using COM briefly and it seemed to work. What I did (re)-disover - however - was that the resolution of directories has a little trap - the redirecting of ROOTDRIVE can cause the path to point to the wrong drive letter - or so it seems. I will have to have a further look. – Stein Åsmul Jul 30 '20 at 18:49
  • [I have an answer on ROOTDRIVE here](https://stackoverflow.com/a/52561165/129130) - not sure that answer is sound advice anymore. Can't look at it right now, but please do read the answer. Information you probably need. – Stein Åsmul Jul 30 '20 at 19:08
1

You can try this though it is not very "polished" or tested.

Using COM:

using System;
using System.Windows.Forms;
using WindowsInstaller;

namespace DTFTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
            var installer = (WindowsInstaller.Installer)Activator.CreateInstance(installerType);
            if (installer == null) { return; }

            var session = installer.OpenProduct("Product-GUID-here");
            if (session == null) { return; }

            session.DoAction("CostInitialize");
            session.DoAction("CostFinalize");

            MessageBox.Show(session.Property["Directory-Property-Here"]);
        }
    }
}

Using DTF:

using System;
using Microsoft.Deployment.WindowsInstaller;
using System.Windows.Forms;

namespace DTFTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Installer.SetInternalUI(InstallUIOptions.Silent);
            var session = Installer.OpenProduct("Product-GUID-here");
            session.DoAction("CostInitialize");
            session.DoAction("CostFinalize");
            MessageBox.Show(session["Directory-Property-Here"]);
            session.Close();
        }
    }
}
Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • So basically with the COM implementation they were likely casting the `Installer` using the wrong type as `Installer` exists in both `WindowsInstaller` and `Microsoft.Deployment.WindowsInstaller`. – user692942 Jul 30 '20 at 18:59
  • Yes, that would probably be it - or some other minor detail, but namespace-issues would be a good guess. Now this ROOTDRIVE issue has me a bit confused and concerned for the sanity of the approach - will have to have a look. The ROOTDRIVE may redirect to the disk with the most disk space. – Stein Åsmul Jul 30 '20 at 19:01
  • [I have an answer on ROOTDRIVE here](https://stackoverflow.com/a/52561165/129130) - not sure that answer is sound advice anymore. Will have a look later. See my other comments to other answers in this question. – Stein Åsmul Jul 30 '20 at 19:09
  • I am aware that the `WindowsInstaller` & `Microsoft.Deployment.WindowsInstallers` needs to be used separately because they both implement `Installer` differently (in fact it would not compile). Like mentioned in the original post, some directories are displayed using COM. Just not all. That is why this is very confusing. If it was not working for all, I can tell mistake is my code. But why would it work partially? – Shintaro Takechi Jul 30 '20 at 19:22
  • The directory property you refer to must be present in the MSI you point to (via product code). If the directory is not defined you get an empty string. If you tested with the same MSI and know that the property is in there, COM should work. Maybe there is something about that collection you use and getting the value? – Stein Åsmul Jul 30 '20 at 19:31
  • Hmm... now that DTF and VBS are getting the desired string, I am sure the way I approach session and DoAction as well as session's property are correct. The dictionary that I pass in to the 2 different methods are from same source. So that should not change either... Trying to double check and tripple check but I cannot seem to pinpoint the issue. Well at least DTF allows me to use C# so I will be satisfy with it for now. If I find the reason I will make sure to add to my answer. – Shintaro Takechi Jul 31 '20 at 01:30