1

I am trying to create my install which offers users to install both for per user and for all users as well.

Now in case lets say John has installed the software at user level and then Andy is trying to install it as machine level on same system, how can i detect that it is already installed at user level for john and either uninstall that or abort the installation?

As i have different functionalities when it is installed at machine level and user level. I don't want conflicting situation on a machine.

Ideally i want to check if software is installed at user level then install it from user level when installing at machine level.

I Am using InstallShield 2016 for creating installers.

CodeJunkie
  • 113
  • 1
  • 11
  • 1
    MSI API: [MsiEnumProductsEx](https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msienumproductsexa) can list installed products. I haven't really used it and I understand there are issues when it comes to enumerating all installations for other users. You get per-user installations for the launching user when you use the older [MsiEnumProducts](https://docs.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msienumproductsa) function. For the newer function I think you have to specify the user SID to list all packages installed for that user. Not 100% sure. Give github.com a search? – Stein Åsmul Aug 30 '19 at 15:14
  • 1
    Not sure if the [NetUserEnum](https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserenum) gives you all users who has logged on with domain accounts. Just adding it here for reference and to perhaps give you something to start with. – Stein Åsmul Aug 30 '19 at 20:53
  • @SteinÅsmul Thanks for the reply. NetUserEnum didn't return domain users logged on to particular machine. It either return all users of local system or all users of domain if you query domain controller. – CodeJunkie Sep 03 '19 at 09:36
  • The third parameter: ``DWORD filter`` - I used ``FILTER_NORMAL_ACCOUNT`` - I can't test domain stuff right now. [NetUserEnum documentation](https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/nf-lmaccess-netuserenum). Will have a look in my bookmarks shortly. – Stein Åsmul Sep 03 '19 at 09:46
  • @SteinÅsmul I used following registry key to get all SIDs of logged in users on a machine. IT has both local as well as domain users. I Have tested it on windows 7 it works fine. HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList. – CodeJunkie Sep 03 '19 at 10:29
  • That sounds good! I will use that myself I think - I have a number of links on the subject, I might update my answer with a little list - feel free to do so yourself (if you have the rights to modify answers yet - otherwise maybe add comments?). – Stein Åsmul Sep 03 '19 at 10:48

2 Answers2

1

Admin Rights: You need to run the below with admin rights for it to work. Launch Visual Studio with admin rights (right click shortcut and go run as adminstrator).


Retrieve Per-User Installations: Didn't have time to clean this up properly, it is quite "dog's breakfast" in terms of mixing all kinds of string types and I smeared it together from github.com snippets, but this seems to work to find the per-user installations for a specified user SID:

#include "pch.h"

#include <windows.h>
#include "msi.h" // Windows Installer
#include <atlstr.h> // ATL CString

#pragma comment (lib, "msi.lib")

int main()
{
    //
    // Admin rights required!
    //

    UINT result = 0;
    DWORD dwIndex = 0;
    TCHAR szInstalledProductCode[39] = { 0 };
    TCHAR szSid[128] = { 0 };
    DWORD cchSid;
    MSIINSTALLCONTEXT dwInstalledContext;
    DWORD cchProductName = MAX_PATH + 1;
    WCHAR* lpProductName = new WCHAR[cchProductName];

    // Fake, sample SID. Replace:
    CString userSID = _T("S-1-5-21-6780625448-452764730-4189743271-1542");

    while (ERROR_SUCCESS == (result = MsiEnumProductsEx(NULL, userSID, MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED, dwIndex, szInstalledProductCode, &dwInstalledContext, szSid, &cchSid)))
    {
        UINT uiReturn = MsiGetProductInfoEx(szInstalledProductCode, userSID, dwInstalledContext, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, lpProductName, &cchProductName);
        MessageBox(NULL, _T("Product Code: ") + (CString)szInstalledProductCode + _T("\r\n\r\nProduct Name: ") + lpProductName, _T("Product Name:"), MB_OK);
        dwIndex++;
    }

    return 0;
}

Procedure: You need to do a few things before the above has any hope of working.

  • Just create a Visual C++ console project. I used Visual Studio 2017.
  • Determine what the user SID is for the account whose per-user packages you want to retrieve. You can use whoami /user from the command line as explained here: How to get active session user SID?
  • Change the CString userSID = "S-1-5-21-etc..." line to have the user SID in question retrieved from your system.

In order to get the list of user SIDs from the system I am not sure what to do. If you have a good approach please share.

There are methods and properties available for VBScripting as well that I tried, but I was not able to retrieve other user's packages. I probably just lacks a few parameters, I am not sure.

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • 1
    Best way i found out is to pass SID as TCHAR * userSID = _T("s-1-1-0"); This will enumerate across all users and return the results. So no Need to look for each users SID. – CodeJunkie Sep 03 '19 at 11:07
  • I am not sure you get other user's installations then, but the current user's installations and the per-machine installations. Not 100% sure. – Stein Åsmul Sep 03 '19 at 11:21
0

This is what worked for me. Thanks to Stein Åsmul.

#include <windows.h>
#include "msi.h" // Windows Installer
#include <tchar.h> // ATL CString

#pragma comment (lib, "msi.lib")
int main()
{
    //
    // Admin rights required!
    //

    TCHAR * userSID = _T("s-1-1-0");
    UINT result = 0;
    DWORD dwIndex = 0;
    MSIINSTALLCONTEXT dwInstalledContext;
    DWORD cchProductName = 2000;
    WCHAR lpProductName[2000] = { 0 };
    while (true)
    {
        TCHAR szInstalledProductCode[100] = { 0 };
        TCHAR szSid[1000] = { 0 };
        DWORD cchSid = 1000;

        if (ERROR_SUCCESS != (result = MsiEnumProductsEx(NULL, userSID, MSIINSTALLCONTEXT_USERMANAGED | MSIINSTALLCONTEXT_USERUNMANAGED, dwIndex, szInstalledProductCode, &dwInstalledContext, szSid, &cchSid)))
        {
            long lVal = GetLastError();
            _tprintf(_T("MsiEnumProductsEx LastError : %d, \r\n\r result: %d\n"), cchSid, result);
            break;
        }
        UINT uiReturn = MsiGetProductInfoEx(szInstalledProductCode, szSid, dwInstalledContext, INSTALLPROPERTY_INSTALLEDPRODUCTNAME, lpProductName, &cchProductName);
        _tprintf(_T("Product Code: %s, \r\n\r\nProduct Name: %s\n"), szInstalledProductCode, lpProductName);
        if (uiReturn != ERROR_SUCCESS)
        {
            long lVal = GetLastError();
            _tprintf(_T(" MsiGetProductInfoEx LastError : %d, \r\n\r result: %d\n"), cchProductName, uiReturn);
        }
        dwIndex++;
    }

    return 0;
}
CodeJunkie
  • 113
  • 1
  • 11