Loading
Local cached MSI does not get deleted when uninstalling
I have an InstallScript MSI Project. I activated the "Cache MSI locally" and indicated the "Cache Path" to "[LocalAppDataFolder]Downloaded Installations".

 

I had a look today into this cache path folder and was really surprised to find a list of folder, not only from the actual installed versions, but also uninstalled versions.

 

Does IS2008 not delete the cached MSI files when uninstalling a version?

 

How do I get IS2008 to delete the cached MSI files during uninstall?

  • It has always been true that cached msi's are left on the target machine. Years ago, we added script to all of our product deployments to clean up msi cache. The script looks at the cache and removes all but the latest one. Recently, when the path for cached msi's changed to [LocalAppDataPath], we adjusted our scirpt to compensate. If you have questions about how to do this, let me know and I will be glad to help.
    Expand Post
  • Would you be willing to share that code? I started writing such a script last night but I'm interested to see how others are doing it to see if there are any aspects that I'm missing.
  • " ITI_Randy wrote:

     

    It has always been true that cached msi's are left on the target machine. Years ago, we added script to all of our product deployments to clean up msi cache. The script looks at the cache and removes all but the latest one. Recently, when the path for cached msi's changed to [LocalAppDataPath], we adjusted our scirpt to compensate. If you have questions about how to do this, let me know and I will be glad to help. "

     

    Yes, I would be interested in that script. Are you willing to share this code with us?
    Expand Post
  • For those who have requested this, I am more than happy to share this code to clean the msi cache on target machines. The example below is for basic msi setups, but can easily be adapted for other project types.

     

    The process involves the following basic steps:

     

    1. Rename the Release cache location:

     

    Since msi's are cached under a GUID-named folder, typically in "Downloaded Installations", it is necessary to rename the release cache folder so that it can be easily located. So for instance, my setup by default may cache to a folder named "C:\Documents and Settings\MyLoginID\Local Settings\Application Data\Downloaded Installations\{2BCC2647-316E-4230-89DF-187395E8EEC5}". My problem will be that the cache for all installations will fall under "Downloaded Installations" in an ambiguous GUID-named folder.

     

    To remedy this, go to Media > Releases in the InstallShield IDE and reset the "Cache path" under setup.exe properties to be more specific. Instead of just "[LocalAppDataFolder]Downloaded Installations", set the path to be "[LocalAppDataFolder]Downloaded Installations\APS1724\MyProgramName". "MyProgramName" is typically the title of your setup that you have in the General Information section of your InstallShield IDE.

     

    Now the GUID-named folders for each particular installation will all fall under the folder for "MyProgramName". This simplifies the process much as you now only have to worry about deleting all but the newest one found in the folder.

     

    2. Write the script to clean up the msi cache. Here is a simple example that pulls the title of the package, assuming that the cache folder name was the package title as mentioned above, and loops through the folder to remove all but the newest GUID-named cache folder found.

     

    export prototype CleanMsiCache(HWND);

     

    function CleanMsiCache(hMSI)

     

    HWND hStream;

     

    STRING sParent, sTitle[39];

     

    NUMBER nDataType, nData, nValueBuf;

     

    POINTER ptrFile;

     

    begin

     

    try

     

    //get the title of this package

     

    MsiGetSummaryInformation (hMSI, "", 0, hStream);

     

    nValueBuf = SizeOf(sTitle);

     

    MsiSummaryInfoGetProperty (hStream, 2, nDataType, nData, ptrFile, sTitle, nValueBuf);

     

    MsiCloseHandle(hStream);

     

    //cleaning can only be done if setup name is found

     

    if (StrLength(sTitle) > 0) then

     

    //purge all but the newest folder in the current cache

     

    sParent = LocalAppDataFolder ^ "Downloaded Installations" ^ sTitle;

     

    if (Is(PATH_EXISTS, sParent) = 1) then

     

    CleanExtraReleaseFolders(hMSI, sParent);

     

    endif;

     

    endif;

     

    catch

     

    endcatch;

     

    end;

     

    prototype CleanExtraReleaseFolders(HWND, STRING);

     

    function CleanExtraReleaseFolders(hMSI, sTargetFolder)

     

    LIST lstDirs;

     

    STRING sValue, sFileName, sCheck, sNewest, sNewestFolder;

     

    STRING sSearchPath, sErrMsg;

     

    NUMBER nResult, nResult2, nDataType, nData, nValueBuf;

     

    VARIANT vErrNum;

     

    begin

     

    try

     

    //check for any residual cache and clean up all but the newest one

     

    lstDirs = ListCreate (STRINGLIST);

     

    //Get all sub folders in the target folder (these will have a GUID name)

     

    nResult = FindAllDirs (sTargetFolder, EXCLUDE_SUBDIR, lstDirs);

     

    if (nResult = 0) then

     

    nResult = ListGetFirstString (lstDirs, sValue);

     

    while (nResult != END_OF_LIST)

     

    //get the file name in the folder

     

    nResult2 = FindAllFiles (sValue, "*.*", sFileName, RESET);

     

    // Get the time the file was last updated

     

    if (nResult2 = 0) then

     

    //file was found in the folder for comparrison

     

    if (sNewest = "") then

     

    sNewest = sFileName; //save the filename

     

    sNewestFolder = sValue; //save the folder name

     

    else

     

    nResult = FileCompare (sFileName, sNewest, COMPARE_DATE);

     

    if nResult = GREATER_THAN then

     

    sNewest = sFileName; //save the filename

     

    sNewestFolder = sValue; //save the folder name

     

    endif;

     

    endif;

     

    else

     

    //no files in the folder, so delete it

     

    DeleteDir(sValue, ROOT);

     

    endif;

     

    nResult = ListGetNextString (lstDirs, sValue);

     

    endwhile;

     

    endif;

     

    /*Loop back through and deleate all but the Newest file.

     

    If a newer file was not located, then cleanup is invalid

     

    because there is nothing to base a comparrision on for cleaning.*/

     

    if (StrLength(sNewestFolder) > 0) then

     

    nResult = ListGetFirstString (lstDirs, sValue);

     

    while (nResult != END_OF_LIST)

     

    if (StrCompare(sValue, sNewestFolder) != 0) then

     

    StrRemoveLastSlash(sValue);

     

    //verify it is a msi cache GUID directory

     

    StrSub(sCheck, sValue, StrLength(sValue) - 1, 1);

     

    if (StrCompare(sCheck, "}") = 0) then

     

    DeleteDir(sValue, ALLCONTENTS);

     

    DeleteDir(sValue, ROOT);

     

    endif;

     

    endif;

     

    nResult = ListGetNextString (lstDirs, sValue);

     

    endwhile;

     

    endif;

     

    catch

     

    endcatch;

     

    end;

     

     

    3. Schedule the custom action to do the cleaning.

     

    Place the custom action in the Execute Sequence after Install Finalize. This will allow the current installation to complete and cache before cleaning is done. Set the condition on the CA to be "REMOVE <> "ALL"".

     

    --------------------------------

     

    You can generically add this script to all install packages and it will always clean all but the latest msi cache. Remember, it is important to leave the latest one cached on the target machine.

     

    An important consideration here is that the location of the msi cache changed with the introduction of Vista/Server 2008. It used to be in the [WindowsFolder]\Downloaded Installations. It is now, as I mentioned above, by default, in the [LocalAppDataPath]Downloaded Installations folder. This was done to follow the new security protocol by Microsoft, where messing with the WindowsFolder is becoming considered a "no-no". This affected our considerations for cache cleanup, as we used to have a script that cleaned the [WindowsFolder] location. I therefore wrote a somewhat more involved script which check to see where the msi was last cached (as it could have been at [WindowsFolder]) and also check the current cache, still removing all but the newest one. For those who are concerned about cleaning the past cache location that may be out there one the target machine, I would be happy to give you that extended script.
    Expand Post
  • 0_M Urman (Flexera Software)

    If it helps any, I'm pretty sure the GUID used in the subfolder name is the package code of the cached setup...
  • Here is what I wrote last night, I'd appreciate feedback. Basically I have a set of mutually exclusive CustomActionData strings that pass in the cache location, current package code and installation mode ( uninstall or everything else ). Inside the script I get a list of cached packages and during uninstall I remove them all and during everything else I remove all of them except for the one currently being installed/upgraded/repaired.

     

     

    Assuming an InstallScript Deffered/System CA and Setup.exe Cache Setting Of:

     

    [CommonAppDataFolder]Downloaded Installations\MyCompany\MyProduct

     

    [CODE]

     

     

     

     

    Not REMOVE="ALL"/>

     

    REMOVE="ALL"/>

     

    [/CODE]

     

    export prototype PurgeCache(HWND);

     

    function PurgeCache(hMSI)

     

    number nResult;

     

    string szInstallMode;

     

    string szCacheRoot;

     

    string szDir;

     

    string szPackageCode;

     

    LIST listDirs;

     

    begin

     

    szInstallMode = MsiGetCustomActionDataAttribute( hMSI, "/InstallMode=" );

     

    szCacheRoot = MsiGetCustomActionDataAttribute( hMSI, "/CacheRoot=" );

     

    szPackageCode = MsiGetCustomActionDataAttribute( hMSI, "/PackageCode=" );

     

    listDirs = ListCreate (STRINGLIST);

     

    FindAllDirs( szCacheRoot, EXCLUDE_SUBDIR, listDirs );

     

    nResult = ListGetFirstString (listDirs, szDir);

     

    while (nResult != END_OF_LIST);

     

    if ( szInstallMode = "Uninstall" || !( szDir &37; szPackageCode )) then

     

    DeleteDir( szDir, ALLCONTENTS );

     

    endif;

     

    nResult = ListGetNextString (listDirs, szDir);

     

    endwhile;

     

    return ERROR_SUCCESS;

     

    end;
    Expand Post
  • Michael,

     

    You are correct in that the GUID is the Package Code. The reason that we did not search directly for this is that we do mostly major version upgrades, so the package code changes for each new version of the setup. The only code that remains the same is the Upgrade Code. In this scenario, the same setup will continue to multiply new GUID-named folders.
    Expand Post
  • Just as a side note, when we started doing cache cleanup, we were warned by InstallShield support early on that there are some issues that could cause us real problems if we are not careful to leave the last cached msi. This is why we looped through the folders deleting all but the latest. In testing, we found that we could really lock up a machine when removing all and then accidently removing a protected component, triggering repair. We would then get caught in a loop with the target machine asking us to locate the msi so it could repair the damage. Even clicking on a desktop icon or attempting to open another app would start the endless loop and message boxes asking to locate the deleted msi cache.
    Expand Post
  • if ( szInstallMode = "Uninstall" || !( szDir % szPackageCode )) then

     

    DeleteDir( szDir, ALLCONTENTS );

     

    endif;

     

    Right. I leave the current package in all situations except full uninstall. I schedule this at the very end of the installation transaction and the MSI cached DB should be being used. I don't think it should be a problem, do you?
    Expand Post
  • Chris,

     

    I like what you wrote and I believe it will work fine. I would throw in a couple of points that may be of assistance.

     

    1. The default location for the cache will be [LocalAppDataPath] and not [CommonAppDataPath]. Early on I pressed InstallShield support staff and developers hard on this issue, as I wanted to get this right since it goes in every package we write. They assured me that the correct path was [LocalAppDataPath]. I was concerned that the cache was being placed under each login rather than in a common location. They told me that this was expected behavior and not a problem and that the location Microsoft recommends is [LocalAppDataPath]. Here is the exact quote given to me from support:

     

    "As we discussed on the phone, the LocalAppDataFolder is the recommended location to cache the MSI file. This is also recommended by Microsoft. This location should still be accessible if the Maintenance is being performed by another user in a Per-machine installation."

     

    Of course, we do all per-machine installations. It is also worth noting that the LocalAppDataPath is the location that will be defaulted in in the newer versions of InstallShield.

     

    2.) It may be of some concern to you as to the left-over cache from older installations of your products which were cached out in the "[WindowsFolder]Downloaded Installations" area. We wanted to get these cleaned up as well, so we added a bit to our script in all newer packages to look there for any legacy cache and purge them. Obviously, this will not be of concern to some as it was to us.
    Expand Post
10 of 28

Loading
Local cached MSI does not get deleted when uninstalling