Hi,

I am trying to upgrade an application. When i run the updated app, it recognises that the app is an upgrade and tries to remove the existing version. However, the existing version was installed from a network share that no longer exists and the locally cached msi has been deleted by someone hoping to save disk space (with no idea what the directory was for). If i copy the msi to the hashed filename in c:\windows\installer, it uninstalls fine, but i need to do this on over 5000 different pc's of different build standards so i cant really goto each one looking at log files for the hashed msi name.

It there a way of specifying a source other than the cached msi to uninstall an application from?? The nearest i have found to this is the "ResolveSource" action but i am unsure if this will fix my problem.

TIA.

Noob.
0 Comments   [ + ] Show Comments

Comments

Please log in to comment

Rating comments in this legacy AppDeploy message board thread won't reorder them,
so that the conversation will remain readable.

Answers

0
msiexec /x <path to original msi> will uninstall the app even if the Windows\Installer folder is empty. So all you have to do is make sure the original MSI is in a location where it can be accessed. I guess whatever distribution tool you use will take care of that.

All you have to do is push the original MSI with an uninstall commandline instead of an install.
Answered 04/28/2010 by: Rheuvel
Brown Belt

Please log in to comment
0
The ResolveSource action does exactly that - it ensures that the source exists. What you want, I believe, is the AddSource method of the WindowsInstaller.Installer object.
Answered 04/28/2010 by: VBScab
Red Belt

Please log in to comment
0
ORIGINAL: Rheuvel

msiexec /x <path to original msi> will uninstall the app even if the Windows\Installer folder is empty. So all you have to do is make sure the original MSI is in a location where it can be accessed. I guess whatever distribution tool you use will take care of that.

All you have to do is push the original MSI with an uninstall commandline instead of an install.



I beg to differ. Running the command you have specified above will (as per the log file) look for the locally cached msi, if it doesnt find it, it will try to run from the "LastUsedSource" which is the unavailable network share. After that, a dialog appears to prompt for a valid location for the source. If i enter a valid source, it works. I cannot do this for 5000 workstns though.

Thanks for trying anyway.
Answered 04/28/2010 by: jk01
Senior Yellow Belt

Please log in to comment
1
Since it's for an unistall, why don't you (in a script for example)

1) copy the MSI of the old version to C:\Temp or wherever
2) overwrite HKCR\Installer\Products\<Hashed Product GUID>\SourceList\LastUsedSource and PackageName with C:\Temp and the name of the MSI respectively

Basically making windows Installer look where you want it to look.

EDIT: for 2) Ian's suggestion, using AddSource, might be a (cleaner) option

PJ
Answered 04/28/2010 by: pjgeutjens
Red Belt

Please log in to comment
0
ORIGINAL: VBScab

The ResolveSource action does exactly that - it ensures that the source exists. What you want, I believe, is the AddSource method of the WindowsInstaller.Installer object. 



Hi VBScab,

I have seen this page already. What i am wondering is how i can get this into my mst?? Does the link contain all of the syntax required??? Im not the best at VB. I have got through the last few yrs with properties and batch files. ;-)
Answered 04/28/2010 by: jk01
Senior Yellow Belt

Please log in to comment
0
Here's a script I've had in my Projects folder for a while...'////////////////////////////////////////////////////////////////////
'/
'/ Name: msisources.vbs
'/ Author: Darwin Sanoy
'/ Updates: http://www.desktopengineer.com
'/ Bug Reports &
' Enhancement Req: [email=Darwin@DesktopEngineer.com]Darwin@DesktopEngineer.com[/email]
'/
'/ Built/Tested On: W2K (NO SP), WSH/VBS 5.5 AND WSH 5.6 Beta 1
'/ Requires: OS: Any
'/ Scripting: WSH/VBS
'/ APIs: MSI
'/
'/ Main Function: Allows active management of MSI source paths.
'/ This includes InstallDir and SourceList properties
'/ Allows "rerooting" of sources (supports subpaths) or
'/ literal paths.
'/
'/ Syntax: msisources.vbs /? for syntax information
'/
'/ Usage and Notes:
'/ For many reasons, this script does not support a global
'/ rerooting of all MSI packages - it uses a control file to
'/ ensure that only packages that intended to be changed are
'/ changed.
'/
'/ MUST BE RUN IN CSCRIPT!
'/ IF YOU ARE JUST TESTING, BACKUP THESE REGISTRY KEYS:
'/ HKEY_CURRENT_USER\Software\Microsoft\Installer\Products
'/ HKEY_CLASSES_ROOT\Installer\Products
'/ HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
'/
'/ THESE KEYS CAN ALSO BE EXAMINED TO VIEW CHANGES MADE BY THIS SCRIPT.
'/
'/ Documentation: msisources.htm
'/
'/ License: GNU General Public License see "license.txt" or
'/ http://www.gnu.org/copyleft/gpl.html
'/
'/ Version: 1.2 (BETA)
'/
'/ Revision History:
'/ 12/8/00 - 1.2 (BETA) - Added "If" on changeinstallsource call to ensure it would
'/ only be called if the source list was being cleared. Added if statements
'/ and removal of last character to prevent extra semicolon
'/ 11/28/00 - 1.12 (BETA) - Help text updated for registry key backup message
'/ and examples
'/ 11/27/00 - 1.0 (BETA) - inital version (djs)
'/
'////////////////////////////////////////////////////////////////////

Const msiInstallStateNotUsed = -7
Const msiInstallStateBadConfig = -6
Const msiInstallStateIncomplete = -5
Const msiInstallStateSourceAbsent = -4
Const msiInstallStateInvalidArg = -2
Const msiInstallStateUnknown = -1
Const msiInstallStateBroken = 0
Const msiInstallStateAdvertised = 1
Const msiInstallStateRemoved = 1
Const msiInstallStateAbsent = 2
Const msiInstallStateLocal = 3
Const msiInstallStateSource = 4
Const msiInstallStateDefault = 5

Dim installer : Set installer = Nothing
Set installer = Wscript.CreateObject("WindowsInstaller.Installer")

Dim product, products, info, productList, version
Set products = installer.Products

Dim oShell: Set oShell = CreateObject("Wscript.Shell")
Dim oFSO : set oFSO = CreateObject("Scripting.FileSystemObject")
Dim Mappings : Set mappings = CreateObject("Scripting.Dictionary")
Dim g_oSwitches : Set g_oSwitches = CreateObject("Scripting.Dictionary")
g_oSwitches.CompareMode = vbTextcompare ' set case insensitivity for arguments
Dim oNet : Set oNet = CreateObject("Wscript.Network")
logonname = LCase(oNet.userdomain) & "\" & LCase(oNet.UserName)

Dim reroot, clearlist, addtoall
Dim g_sScriptEngine


' Script Version
g_sVersion = "1.2 (BETA)"

MakeDesiredHost "cscript.exe"

' Show signon banner
ix = Instr(Wscript.ScriptName, ".")
If ix <> 0 Then s = Left(Wscript.ScriptName, ix - 1) Else s = Wscript.ScriptName
Wscript.Echo s & " version " & g_sVersion & vbCrlf

'Parse Command Line Arguments
ParseArgs

'Validate arguments, check for help request
AssignAndValidateArgs


'////////////////////////////////////////////////////////////////////////////
' Main Code
'
'
If UCase(g_oSwitches("dumpmsipaths")) = UCase("YES") Then
dumpmsipaths g_oSwitches("controlfile")
Wscript.Quit(0)
End If

BuildControlList 'Build Control list from control file

wscript.echo vbNewline & "TEST EACH PRODUCT"
For Each product In products
'wscript.echo product & " = " & installer.ProductInfo(product, "ProductName")
If mappings.item(product) <> "" Then
If clearlist Then
clearsource product, logonname
End If

' Build pathstring depending on mode of operation
If reroot Then
pathstring=""
rootpath=split(g_oSwitches("roots"),";")
subpath=split(mappings.item(product),";")
For x = 0 to UBound(rootpath)
If Right(rootpath(x),1) <> "\" Then rootpath(x) = rootpath(x) & "\"
For y = 0 to UBound(subpath)
pathstring = pathstring & rootpath(x) & subpath(y) & ";"
Next
pathstring = left(pathstring,len(pathstring)-1)
Next
If addtoall <> "" Then
If Right(pathstring,1) <> ";" Then pathstring = pathstring & ";"
pathstring = pathstring & addtoall
End If
Else
If addtoall <> "" Then
If Right(addtoall,1) <> ";" Then addtoall = addtoall & ";"
End If
pathstring = addtoall & mappings.item(product)
End If

wscript.echo "Complete Path String " & pathstring
paths = split(pathstring, ";")

'If the package code has an install source, change it to the first path
If clearlist Then
Changeinstallsource product, paths(0)
End If

'Process all paths in Pathstring
For x = 0 to UBound(paths)
addsourcelistentry product, paths(x), logonname
Next

End If
Next
Set products = Nothing
Wscript.Quit 0

'////////////////////////////////////////////////////////////////////////////
' ChangeInstallSource
' Change the "InstallSource" property of an MSI product.
' This cannot be done through the MSI APIs - so it is done through the registry
'
Sub changeinstallsource(productcode, path)
On Error Resume Next
Uninstallkey = "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" & productcode & "\InstallSource"
currentkeyvalue = oShell.Regread(Uninstallkey)
If Err = 0 Then
oShell.RegWrite Uninstallkey, path, "REG_SZ"
End If
On Error GoTo 0
End Sub

'////////////////////////////////////////////////////////////////////////////
' AddSourceListEntry
' add a source list entry to the product
'
Sub addsourcelistentry(productcode, addsource, username)
On Error Resume Next
Dim installer2 : Set installer2 = Nothing
Set installer2 = Wscript.CreateObject("WindowsInstaller.Installer")
Wscript.echo "Setting Path for: " & productcode & " to " & addsource

If installer2.ProductState(product) <> msiInstallStateDefault Then
rc = installer2.AddSource(productcode,username,addsource)
Else
rc = installer2.AddSource(productcode,"",addsource)
End If
On Error GoTo 0
End Sub

'////////////////////////////////////////////////////////////////////////////
' ClearSource
' Clear the MSI Source list from a product
'
'
Sub clearsource(productcode, username)
On Error Resume Next
Dim installer3 : Set installer3 = Nothing
Set installer3 = Wscript.CreateObject("WindowsInstaller.Installer")
Wscript.echo "Clearing Sourcelist for: " & productcode

If installer3.ProductState(product) <> msiInstallStateDefault Then
rc = installer3.ClearSourceList(productcode,username)
Else
rc = installer3.ClearSourceList(productcode,"")
End If
On Error GoTo 0
End Sub

'////////////////////////////////////////////////////////////////////////////
' BuildControlList
' Build control list from disk file
'
'
Sub buildcontrollist()
wscript.echo vbNewline & "BUILD DICTIONARY FROM FILE"
mappings.comparemode = 1

Set controlfile = oFSO.OpenTextFile(g_oSwitches("controlfile"), 1)
Do while controlfile.AtEndOfStream <> True
Line = controlfile.Readline
wscript.echo "Read Line: " & Line
pair = split(line, "|")
mappings.add Trim(pair(0)), Trim(pair(1))
Loop
End Sub
'////////////////////////////////////////////////////////////////////////////

'////////////////////////////////////////////////////////////////////////////
' ParseArgs
' Parse the arguments using Split function
'
'
Sub ParseArgs
Dim pair, list, sArg, Item
For Each sArg In Wscript.Arguments
pair = Split(sArg, "=", 2)

'if value is specified multiple times, last one wins
If g_oSwitches.Exists(Trim(pair(0))) Then
g_oSwitches.Remove(Trim(pair(0)))
End If

If UBound(pair) >= 1 Then
g_oSwitches.add Trim(pair(0)), Trim(pair(1))
Else
g_oSwitches.add Trim(pair(0)),""
End If
Next
If g_nTraceLevel > 0 Then
For each Item in g_oSwitches
list = list & Item & "=" & g_oSwitches(Item) &vbNewline
Next
wscript.echo list
End If
End Sub

'////////////////////////////////////////////////////////////////////////////

'////////////////////////////////////////////////////////////////////////////
' AssignAndValidateArgs
' Error check arguments and setup switches
'
'
Sub AssignAndValidateArgs
' Check for -help, -? etc help request on command line
If (g_oSwitches.count < 1) Or (g_oSwitches.Exists("help")) Or (g_oSwitches.Exists("/help")) Or (g_oSwitches.Exists("?")) Or (g_oSwitches.Exists("/?")) Then
ShowHelpMessage
Wscript.Quit(1)
End If

'Validate dependencies
If not g_oSwitches.Exists("controlfile") Then
wscript.echo "Must specifiy a control file using ""controlfile=<filename>"" "
wscript.quit 1
End If

If Not oFSO.FileExists(g_oSwitches("controlfile")) And ucase(g_oSwitches("dumpmsipaths")) <> "YES" Then
wscript.echo "The control file """ & g_oSwitches("controlfile") & """ does not exist."
wscript.quit 1
End If

If ucase(g_oSwitches("reroot")) = "YES" And g_oSwitches("roots") = "" Then
wscript.echo "If ""reroot=yes"" you must also specify ""roots=<pathname>;<pathname>..."" "
wscript.quit 1
End If

If g_oSwitches("roots") <> "" And ucase(g_oSwitches("reroot")) <> "YES" Then
wscript.echo """reroot"" does not equal ""YES"", ignoring argument ""roots=" & g_oSwitches("roots") & """"
wscript.quit 1
End If

If g_oSwitches.Exists("addtoall") Then addtoall = g_oSwitches("addtoall")
If UCase(g_oSwitches("clearlist")) = UCase("YES") Then clearlist = true
If UCase(g_oSwitches("reroot")) = UCase("YES") Then reroot = True

End Sub
'////////////////////////////////////////////////////////////////////////////


'////////////////////////////////////////////////////////////////////////////
' DumpMSIPaths
' Parse the arguments using Split function
'
'
Sub DumpMSIPaths(controlfile)
Dim product, products, info, productList, version, InstSource
Dim installer4 : Set installer4 = Wscript.CreateObject("WindowsInstaller.Installer")
Dim fs : Set fs = CreateObject("Scripting.FileSystemObject")
Dim auditfile : Set auditfile = fs.CreateTextFile(controlfile, true)

Set products = installer4.Products
For Each product In products
version = DecodeVersion(installer4.ProductInfo(product, "Version"))
On Error Resume Next
InstSource = installer4.ProductInfo(product, "InstallSource")
If Err <> 0 Then Err.Clear
info = product & " = " & installer4.ProductInfo(product, "ProductName") & " " & version & " " & InstSource
If productList <> Empty Then productList = productList & vbNewLine & info Else productList = info
auditfile.WriteLine product & "|" & InstSource & "|" & installer4.ProductInfo(product, "ProductName") & "|" & version & "|" & installer4.ProductInfo(product, "PackageName")
InstSource=""
Next
If productList = Empty Then productList = "No products installed or advertised"
Wscript.Echo productList
Set products = Nothing
auditfile.Close
Wscript.Quit 0
End Sub

'////////////////////////////////////////////////////////////////////////////

'////////////////////////////////////////////////////////////////////////////
' MakeDesiredHost
' Make the script run in the desired host
'
'
Sub MakeDesiredHost(desiredhost)
g_sScriptEngine = lcase(mid(WScript.FullName, InstrRev(WScript.FullName,"\")+1))
Dim oShell : Set oShell = createobject("wscript.shell")
comspec = oShell.Environment("Process").Item("COMSPEC")
If Not g_sScriptEngine=desiredhost Then
' re-launch script using the desired host
Set oShell = CreateObject("WScript.Shell")
Set objArgs = WScript.Arguments
For I = 0 To objArgs.Count - 1
args = args & " " & objArgs(I)
Next
' uncomment next line for debugging
'wscript.echo comspec & " /k " & desiredhost & " """ & wscript.scriptfullname & """" & args
RunCmd = oShell.Run(comspec & " /k " & desiredhost & " """ & wscript.scriptfullname & """" & args, 1, True)
WScript.Quit
end if
End Sub
'////////////////////////////////////////////////////////////////////////////


'////////////////////////////////////////////////////////////////////////////
' DecodeVersion
' Decode MSI version string
'
'
Function DecodeVersion(version)
version = CLng(version)
DecodeVersion = version\65536\256 & "." & (version\65535 MOD 256) & "." & (version Mod 65536)
End Function

'////////////////////////////////////////////////////////////////////////////

'////////////////////////////////////////////////////////////////////////////
' ShowHelpMessage
' Display help message
'
Sub ShowHelpMessage
Wscript.Echo " This script helps manage MSI source paths." &_
vbNewLine & " " &_
vbNewLine & " MUST BE RUN IN CSCRIPT!" &_
vbNewLine & " " &_
vbNewline & " IF YOU ARE JUST TESTING, BACKUP THESE REGISTRY KEYS:" &_
vbNewline & " HKEY_CURRENT_USER\Software\Microsoft\Installer\Products" &_
vbNewline & " HKEY_CLASSES_ROOT\Installer\Products" &_
vbNewline & " HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" &_
vbNewline & " " &_
vbNewline & " THESE KEYS CAN ALSO BE EXAMINED TO VIEW CHANGES MADE BY THIS SCRIPT." &_
vbNewline & " " &_
vbNewLine & " controlfile=<filename> " &_
vbNewLine & " {Required, No default}" &_
vbNewLine & " The name of the control file. Used for output when using dumpmsipaths." &_
vbNewLine & " " &_
vbNewLine & " clearlist=yes" &_
vbNewLine & " {not required, default=no}" &_
vbNewLine & " Causes each source list to be cleared before" &_
vbNewLine & " the new paths are added." &_
vbNewLine & " " &_
vbNewLine & " addtoall=<path>;<path>;..." &_
vbNewLine & " {not required, no default}" &_
vbNewLine & " A list of comma delimited literal path references to be" &_
vbNewLine & " added to the source list for each package listed in the" &_
vbNewLine & " control file. The listed paths will be the first added" &_
vbNewLine & " to the source list." &_
vbNewLine & " " &_
vbNewLine & " reroot=yes" &_
vbNewLine & " {not required, default=no, requires ""roots"" argument}" &_
vbNewLine & " Causes script to run in ""reroot"" mode. In this" &_
vbNewLine & " mode the script takes a list of path roots and appends them" &_
vbNewLine & " to each subpath in the control file and adds all resultant" &_
vbNewLine & " paths to the source list for the specific application." &_
vbNewline & " " &_
vbNewLine & " Press ENTER to Continue... "

If lcase(mid(WScript.FullName, InstrRev(WScript.FullName,"\")+1)) = "cscript.exe" Then
tmp = wscript.stdIn.Readline 'wait for keypress
End If

Wscript.Echo " dumpmsipaths=yes" &_
vbNewLine & " {not required, default=no, requires ""controlfile"" argument}" &_
vbNewline & " [Takes precedent over switches for updates (addtoall, clearlist & reroot]" &_
vbNewline & " Causes current MSI paths to be dumped to the file specified" &_
vbNewLine & " in the ""controlfile"" argument. The MSI paths are only dumped" &_
vbNewLine & " if the installed package has a ""InstallSource"" property. All" &_
vbNewLine & " MSI packages have a SOURCELIST that cannot be read by this script." &_
vbNewline & " The SOURCELIST as well as the INSTALLSOURCE property are changed" &_
vbNewLine & " by this script when it is run." &_
vbNewLine & " " &_
vbNewline & " roots=<path>;<path>;..." &_
vbNewLine & " {not required, default=no, requires ""roots"" argument}" &_
vbNewLine & " A list of comma delimited path root references to be appended" &_
vbNewLine & " to each subpath in the control file." &_
vbNewLine & " " &_
vbNewLine & " /help, help, /?, ?" &_
vbNewLine & " {not required}" &_
vbNewline & " [Takes precedent over all other switches]" &_
vbNewline & " Display help." &_
vbNewline & " " &_
vbNewLine & " Press ENTER to Continue... "

If lcase(mid(WScript.FullName, InstrRev(WScript.FullName,"\")+1)) = "cscript.exe" Then
tmp = wscript.stdIn.Readline 'wait for keypress
End If

Wscript.Echo " EXAMPLES:" &_
vbNewline & " " &_
vbNewLine & " Dump Existing MSI Paths to a template control file" &_
vbNewLine & " ""cscript msisources.vbs controlfile=currentmsi.txt dumpmsipaths=yes""" &_
vbNewLine & " " &_
vbNewLine & " Update MSI paths with literal paths. Addtoall path list will be added" &_
vbNewLine & " to each source list. Paths in addtoall are always added first then" &_
vbNewLine & " paths from the control file for each individual application. The first" &_
vbNewLine & " path in addtoall becomes the SourceDir for packages that have a SourceDir." &_
vbNewLine & " This command also clears the existing path list before updating." &_
vbNewLine & " " &_
vbNewLine & " ""cscript msisources.vbs addtoall=\\srv1\shr1\pkg;\\srv2\install;n:\install" &_
vbNewLine & " controlfile=newmsi.txt clearlist=yes""" &_
vbNewLine & " " &_
vbNewLine & " Update MSI paths by rerooting. Each path in the control file is appended" &_
vbNewLine & " to each path in the ""roots"" path list. The first path in roots is always" &_
vbNewLine & " the first in the sourcelist and becomes the SourceDir." &_
vbNewLine & " This command also clears the existing path list before updating." &_
vbNewLine & " " &_
vbNewLine & " ""cscript msisources.vbs roots=\\srv1\shr1\pkg;\\srv2\install;n:\install" &_
vbNewLine & " controlfile=newmsi.txt reroot=yes clearlist=yes"""
Wscript.Quit 0
End Sub

'////////////////////////////////////////////////////////////////////////////
Im not the best at VB.VBS is easy. Good VBS is harder. Just remember my golden rule: assume that nothing will work. That is to say, error-trap E V E R Y T H I N G. For example, if the above were *my* code, the single lineSet installer = Wscript.CreateObject("WindowsInstaller.Installer")would be four lines:Set installer = Wscript.CreateObject("WindowsInstaller.Installer")
If Not IsObject(installer) Then
'// Display/log an appropriate error message
WScript.Quit(False)
End If
Answered 04/28/2010 by: VBScab
Red Belt

Please log in to comment
0
ORIGINAL: pjgeutjens

Since it's for an unistall, why don't you (in a script for example)

1) copy the MSI of the old version to C:\Temp or wherever
2) overwrite HKCR\Installer\Products\<Hashed Product GUID>\SourceList\LastUsedSource and PackageName with C:\Temp and the name of the MSI respectively

Basically making windows Installer look where you want it to look.

EDIT: for 2) Ian's suggestion, using AddSource, might be a (cleaner) option

PJ


Im sure VBScab's method would work but the script looks a bit daunting.

Initially i placed the reg entry in a transform but then realised the /x will not read the msi that i have supplied as source.

So I copied the source file to the local disk and amended the required registry entry to reflect the location in a VB script prior to running the MSi and it worked.

Thanks v much PJ. Have a point.
Answered 04/29/2010 by: jk01
Senior Yellow Belt

Please log in to comment
Answer this question or Comment on this question for clarity