How to use VBA to uninstall a program with msiexec.exe

I am trying to imitate the uninstalling of a program with VBA to save the user doing it.

The program is a plugin so it only has an unistall shortcut with the following target.  It is the google earth plugin.

"C:\WINNT\system32\msiexec.exe /x {2862C000-331E-11DF-89BF-005056806466} FEEDBACK=1"

This creates a User interface and as far as I can see the FEEDBACK=1 opens up a website at the end.

If I change the target to (as you would know /qn should just make it silent)

"C:\WINNT\system32\msiexec.exe /x {2862C000-331E-11DF-89BF-005056806466} FEEDBACK=0  /qn"

and then double click the button then it uninstalls silently as expected and nothing else happens.  Files/Directory and registry is clean after a while.

The problem is when I use the code below - then after it is uninstalled windows restarts. After the restart the files are gone and registry is clean but the directories are still there.

Could anyone give me a hint why this happens when I call the command from shellandwait????

Or is there are log that says which program forced XP to restart?

EDIT:I think I might have found out that it closes down if the GEPlugin.exe is still a process.  It appears that it does not end immediately - so maybe the answer to this question is how to get access to check if the process is running?

Call shellAndWait("C:\WINNT\system32\msiexec.exe /x {2862C000-331E-11DF-89BF-005056806466} FEEDBACK=0  /qn", vbHide)

'my shell and wait looks like this

Public Sub shellAndWait(ByVal strProg As String, _
ByVal lStyle As VbAppWinStyle)

Dim ProcessId As Long
Dim ProcessHandle As Long
Const ACCESS As Long = &H100000

ProcessId = Shell(strProg, lStyle)
ProcessHandle = OpenProcess(ACCESS, False, ProcessId)
If ProcessHandle <> 0 Then
CloseHandle ProcessHandle
End If
Loop Until ProcessHandle = 0

End Sub

Open in new window

LVL 20
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Kyle AbrahamsSenior .Net DeveloperCommented:

you can get processes from here.  What if you did this just with a normal bat file?  
darbid73Author Commented:
Thanks ged325 for the quick answer....

I am pretty much on top of the process part with code like this

The question is still why it is restarting.

This same Msaccess program is also hosting a webbroswer control with the Google earth program - maybe if the webbrowser is still "holding onto the webpage" or something that it is forcing windows to restart.  ???

Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long


Private Declare Function CloseHandle Lib "kernel32" ( _
    ByVal hObject As Long) As Long

Private Declare Function EnumProcesses Lib "PSAPI.DLL" ( _
   lpidProcess As Long, ByVal cb As Long, cbNeeded As Long) As Long

Private Declare Function EnumProcessModules Lib "PSAPI.DLL" ( _
    ByVal hProcess As Long, lphModule As Long, ByVal cb As Long, lpcbNeeded As Long) As Long

Private Declare Function GetModuleBaseName Lib "PSAPI.DLL" Alias "GetModuleBaseNameA" ( _
ByVal hProcess As Long, ByVal hModule As Long, ByVal lpFileName As String, ByVal nSize As Long) As Long

Private Const PROCESS_VM_READ = &H10


Private Function IsProcessRunning(ByVal sProcess As String) As Boolean

    Const MAX_PATH As Long = 260

    Dim lProcesses() As Long, lModules() As Long, N As Long, lRet As Long, hProcess As Long

    Dim sName As String


    sProcess = UCase$(sProcess)


    ReDim lProcesses(1023) As Long

    If EnumProcesses(lProcesses(0), 1024 * 4, lRet) Then

        For N = 0 To (lRet \ 4) - 1

            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION Or PROCESS_VM_READ, 0, lProcesses(N))

            If hProcess Then

                ReDim lModules(1023)

                If EnumProcessModules(hProcess, lModules(0), 1024 * 4, lRet) Then

                    sName = String$(MAX_PATH, vbNullChar)

                    GetModuleBaseName hProcess, lModules(0), sName, MAX_PATH

                    sName = Left$(sName, InStr(sName, vbNullChar) - 1)

                    If Len(sName) = Len(sProcess) Then

                        If sProcess = UCase$(sName) Then IsProcessRunning = True: Exit Function

                    End If

                End If

            End If

            CloseHandle hProcess

        Next N

    End If

End Function

Open in new window

There is a problem with the ShellAndWait method that you use. It's not a very good idea to check the process handle in a loop in such a way. I recommend that you take a different approach and pass the process handle to one of the wait functions such as MsgWaitForMultipleObjects() that way your application can be signaled when the process has exited. Currently you are opening multiple processes handles which means that in such a loop it's possible to run out of memory.
1) Do you know if the package has the force remove option?
2) Does this only occur when using the silent option but without that option folders are removed?
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

darbid73Author Commented:
hi eql1044

I have been using this shellandwait function for some time for a number of things.  I will have to research your suggestion for code.

Would "such a loop it's possible to run out of memory" cause windows to restart?  I am wondering which program is setting the windows flag to do a restart as opposed to a simple shut down.

to answer your questions.

1.  I do not know. How can I test for this force remove - or where should I look? I can see the registry uninstall key but nothing shows there.

2. it appears that the ONLY time this happens is if I use my shellandwait function WITH the "\qn" and "vbhide".  That means if I do not use VBA or shellandwait and simply change the "target" with "\qn" or use CMD I do not get any problems.  Also if I use shellandwait wihtOUT the \qn then there is also no problem.
I can't be sure if it would restart your computer. I would think the system would throw a message box and tell you the system is running low on memory and attempt to terminate your application to free up resource however the possibility of the system restarting is possible. The handle you create is allocated in the page pool so if they method runs long enough it will use all the available memory in your process leading to alot of nasty behavior.
We could try to determine if the problem is with your function or not could you try to run that same line from a command prompt and see what side effects if any happen?
I found one my old post on EE so I would recommend you upate to the following.

' Class1.cls
Option Explicit
Private Const WAIT_OBJECT_0       As Long = 0
Private Const SYNCHRONIZE         As Long = &H100000
Private Const INFINITE            As Long = &HFFFFFFFF
Private Const QS_HOTKEY           As Long = &H80
Private Const QS_KEY              As Long = &H1
Private Const QS_MOUSEBUTTON      As Long = &H4
Private Const QS_MOUSEMOVE        As Long = &H2
Private Const QS_PAINT            As Long = &H20
Private Const QS_POSTMESSAGE      As Long = &H8
Private Const QS_SENDMESSAGE      As Long = &H40
Private Const QS_TIMER            As Long = &H10
Private Const QS_ALLINPUT         As Long = (QS_SENDMESSAGE _
  Or QS_KEY)
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function MsgWaitForMultipleObjects Lib "user32" (ByVal nCount As Long, pHandles As Long, ByVal fWaitAll As Long, ByVal dwMilliseconds As Long, ByVal dwWakeMask As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Event OnProcessClose()
Public PrematureExit  As Boolean
Private hProcess      As Long
Private mPid          As Long
Public Property Get IsWaiting() As Boolean
  If hProcess <> 0 Then
    IsWaiting = True
    IsWaiting = False
  End If
End Property
Public Sub ShellAndWait(ByVal arg As String)
  ' Wait on a single process to close.
  Dim bWait   As Long
  mPid = Shell(arg, vbNormalFocus)
  hProcess = OpenProcess(SYNCHRONIZE, 0, mPid)
  If hProcess = 0 Then
    Exit Sub
  End If
    bWait = MsgWaitForMultipleObjects(1, hProcess, 0, INFINITE, QS_ALLINPUT)
      If PrematureExit Then
        bWait = WAIT_OBJECT_0
      End If
  Loop Until bWait = WAIT_OBJECT_0
  CloseHandle hProcess
  hProcess = 0
  If PrematureExit = False Then
    RaiseEvent OnProcessClose
  End If
End Sub

' Form1.frm

Option Explicit
Dim WithEvents cProcessWait As Class1
Private Sub Command1_Click()
  If cProcessWait.IsWaiting = False Then
    cProcessWait.ShellAndWait "c:\windows\system32\calc.exe"
    Debug.Print "Already waiting...."
  End If
End Sub
Private Sub cProcessWait_OnProcessClose()
  Debug.Print "Process has closed : " & Time
End Sub
Private Sub Form_Load()
  Set cProcessWait = New Class1
End Sub
Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
  cProcessWait.PrematureExit = True
  Set cProcessWait = Nothing
End Sub

Open in new window


Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
BTW: It appears there is two spaces in your command line just after the feedback. I don't know if that is intended or can cause a potential problem but I just want to point it out now just incase.
This line... FEEDBACK=0  /qn
darbid73Author Commented:
I am working on this - I migh have a solution generally but do not want to jump to any conclusions - I have added a "REBOOT=ReallySuppress" to the command and on my first test it has not rebooted.

This does not mean that my shellandwait is not also a problem but I am making progress.
Your ShellAndWait is a problem :) but it might not be causing the problem with the installer. The CPU usage and general performance overall will be much better once you remove that current routine. :)
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Microsoft Access

From novice to tech pro — start learning today.