{"id":452,"date":"2025-08-01T09:32:39","date_gmt":"2025-08-01T06:32:39","guid":{"rendered":"https:\/\/kb.astrocroc.com\/?p=452"},"modified":"2025-08-01T09:32:39","modified_gmt":"2025-08-01T06:32:39","slug":"%d0%b5%d1%81%d0%bb%d0%b8-%d0%bd%d0%b5-%d1%83%d1%81%d1%82%d0%b0%d0%bd%d0%b0%d0%b2%d0%bb%d0%b8%d0%b2%d0%b0%d1%8e%d1%82%d1%81%d1%8f-%d0%be%d0%b1%d0%bd%d0%be%d0%b2%d0%bb%d0%b5%d0%bd%d0%b8%d1%8f-%d0%bd","status":"publish","type":"post","link":"https:\/\/kb.astrocroc.com\/?p=452","title":{"rendered":"\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 SharePoint"},"content":{"rendered":"\n<p>\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0432\u0435\u0440 SharePoint \u0442\u043e\u0439 \u0436\u0435 \u0432\u0435\u0440\u0441\u0438\u0438, \u0433\u0434\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f. <\/p>\n\n\n\n<p>\u0421\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0430\u043f\u043a\u0443 c:\\windows\\installer \u0441 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 SharePoint \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e c:\\temp\\ \u043f\u0430\u043f\u043a\u0443 \u043d\u0430 \u0432\u0430\u0448 \u043f\u043b\u043e\u0445\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440.<\/p>\n\n\n\n<p>\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b OpUtil.vbs \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-vbnet\" data-line=\"\">&#039;=======================================================================================================\n&#039; Name: OPUtil.vbs - Office Patch Utility\n&#039; Author: Microsoft Customer Support Services\n&#039; Copyright (c) 2009, Microsoft Corporation\n&#039; \n&#039; Utility for Office patch maintenance tasks &quot;log, repair, apply, remove, clean&quot;\n&#039; Formerly published as MspFixUp.vbs\n&#039;\n&#039;=======================================================================================================\n\n&#039;=======================================================================================================\n&#039;[INI] Section for script behavior customizations\n\n&#039;Quiet switch.\n&#039;Default: False -&gt; A summary log opens automatically when done\nDim bQuiet\nbQuiet = False\n\n&#039;Set fDetectOnly to &#039;True&#039; if only a log should be generated\n&#039;None of the detected actions required will be executed!\n&#039;Default: False -&gt; execute detected actions required\nDim fDetectOnly\nfDetectOnly = False\n\n&#039;Optional location to provide .msp patch files that should be applied\n&#039;A list of full path references to folders with .msp files, separated by semicolons\n&#039;Default: sUpdateLocation = &quot;&quot;\nDim sUpdateLocation\nsUpdateLocation = &quot;&quot;\n\n&#039;Optional location to restore .msi &amp; .msp packages that have gone missing\n&#039;A list of fully qualified paths to folders with .msi and\/or .msp files, separated by semicolons\n&#039;Default: sUpdateLocation = &quot;&quot;\nDim sRestoreLocation\nsRestoreLocation = &quot;&quot;\n\n&#039;Option to explicitly exclude the Windows Installer cache when searching for applicable .msp files.\n&#039;This allows to enforce patches are only applied from the provided SUpdateLocation folders\n&#039;Note: For detection integrity it&#039;s still required to include the patches in the sequence logic!\n&#039;Default: fExcludeCache = False -&gt; Scans %WinDir%\\Installer folder for applicable patches\nDim fExcludeCache\nfExcludeCache = False\n\n&#039;Option to include OCT patches from the Windows Installer cache.\n&#039;This option is a subset of the above &#039;fExcludeCache&#039; option\n&#039;it allows to include that cached (installed) OCT patches get applied\n&#039;NOTE: It&#039;s recommended to keep this set to &#039;False&#039; unless you have a specific requirement to enforce this\n&#039;Default: fIncludeOctCache = False -&gt; Filters OCT patches from %WinDir%\\Installer folder\nDim fIncludeOctCache\nfIncludeOctCache = False\n\n&#039;Check the integrity of the local Windows Installer cache and try to repair missing .msi and .msp files if needed\n&#039;Default: fRepairCache = True -&gt; Try to repair missing files\nDim fRepairCache\nfRepairCache = True\n\n&#039;Unregister patches that have gone missing from the local Windows Installer cache to unblock maintenance transactions\n&#039;Default: fReconcileCache = False -&gt; Don&#039;t unregister missing patches\nDim fReconcileCache\nfReconcileCache = False\n\n&#039;Apply .msp patch files\n&#039;Patch files are applied from the optional SUpdateLocation folders and the local Windows Installer cache.\n&#039;Note: To fine tune the behavior see the options for SUpdateLocation, fExcludeCache, fIncludeOctCache\n&#039;Default: fApplyPatch = True -&gt; Apply patches\nDim fApplyPatch\nfApplyPatch = True\n\n&#039;Remove installed patches\n&#039;Allows to uninstall superseded patches or a specified list.\n&#039;The list allows passing in a KB number(s) or PatchCode(s)\n&#039;Default: fRemovePatch = False -&gt; Do not attempt to uninstall patches\n&#039; sMspRemoveFilter = &quot;Superseded&quot; -&gt; If enabled default to remove superseded patches\n&#039; sMspProductFilter = &quot;&quot; -&gt; If enabled remove from all products\nDim fRemovePatch,sMspRemoveFilter,sMspProductFilter\nfRemovePatch = False\nsMspRemoveFilter = &quot;Superseded&quot;\nsMspProductFilter= &quot;&quot;\n\n&#039;Delete cached .msp files that are no longer referenced by any product from the local Windows Installer cache\nDim fCleanCache\nfCleanCache = False\n\n&#039;Suppress debug logging details from the log\n&#039;Default: fNoDebugLog = False -&gt; Add debug logging information\nDim fNoDebugLog\nfNoDebugLog = False\n\n&#039;DO NOT CUSTOMIZE BELOW THIS LINE!\n&#039;=======================================================================================================\n\n&#039;Declarations\n\nConst SOLUTIONNAME = &quot;OPUtil&quot;\nConst SCRIPTBUILD = &quot;2.24&quot;\n\nConst FOR_READING = 1\nConst FOR_WRITING = 2\nConst FOR_APPENDING = 8\nConst TRISTATE_TRUE = -1\nConst TRISTATE_USEDEFAULT = -2\nConst LEN_GUID = 38\nConst USERSID_NULL = &quot;&quot;\nConst USERSID_EVERYONE = &quot;s-1-1-0&quot;\n\nConst PID_TITLE = 2\nConst PID_SUBJECT = 3 &#039;Displayname\nConst PID_AUTHOR = 4 &#039;Author\nConst PID_COMMENTS = 6 &#039;Comments\nConst PID_TEMPLATE = 7 &#039;compatible platform and language versions for .msi \/ PatchTargets for .msp\nConst PID_LASTAUTHOR = 8 &#039;Transform Substorages\nConst PID_REVNUMBER = 9 &#039;package code for .msi \/ GUID patch code for .msp\nConst PID_WORDCOUNT = 15 &#039;minimum Windows Installer version \nConst PID_SECURITY = 19 &#039;read-only flag\n\n&#039;Component state constants\nConst INSTALLSTATE_NOTUSED = -7 &#039; component disabled\nConst INSTALLSTATE_UNKNOWN = -1 &#039; unrecognized product or feature\nConst INSTALLSTATE_BROKEN = 0 &#039; broken\nConst INSTALLSTATE_LOCAL = 3 &#039; installed. \nConst INSTALLSTATE_SOURCE = 4 &#039; installed to run from source, CD, or network.\n\nConst MSICOLUMNINFONAMES = 0\nConst MSICOLUMNINFOTYPES = 1\n\nConst MSIINSTALLCONTEXT_USERMANAGED = 1\nConst MSIINSTALLCONTEXT_USERUNMANAGED = 2\nConst MSIINSTALLCONTEXT_MACHINE = 4\nConst MSIINSTALLCONTEXT_ALL = 7\nConst MSIPATCHSTATE_APPLIED = 1\nConst MSIPATCHSTATE_SUPERSEDED = 2\nConst MSIPATCHSTATE_OBSOLETED = 4\nConst MSIPATCHSTATE_REGISTERED = 8\nConst MSIPATCHSTATE_ALL = 15\n\nConst MSIOPENDATABASEMODE_READONLY = 0\nConst MSIOPENDATABASEMODE_TRANSACT = 1\nConst MSIOPENDATABASEMODE_PATCHFILE = 32\n\nConst MSISOURCETYPE_NETWORK = 1\nConst MSISOURCETYPE_URL = 2\n\nConst MSIUILEVEL_BASIC = 3\nConst MSIUILEVEL_PROGRESSONLY = 64\nConst MSIINSTALLTYPE_SINGLEINSTANCE = 2\nConst MSIINSTALLSTATE_LOCAL = 3\n\nConst MSITRANSFORMERROR = 256\nConst MSITRANSFORMERROR_ALL = 319\n\nConst HKCR = &amp;H80000000\nConst HKCU = &amp;H80000001\nConst HKLM = &amp;H80000002\nConst HKU = &amp;H80000003\nConst HKEY_CLASSES_ROOT = &amp;H80000000\nConst HKEY_CURRENT_USER = &amp;H80000001\nConst HKEY_LOCAL_MACHINE = &amp;H80000002\nConst HKEY_USERS = &amp;H80000003\nConst MSP_NOSEQ = 0\nConst MSP_MINOR = 1\nConst MSP_SMALL = 2\nConst COL_FILENAME = 0\nConst COL_TARGETS = 1\nConst COL_PATCHCODE = 2\nConst COL_SUPERSEDES = 3\nConst COL_KB = 4\nConst COL_PACKAGE = 5\nConst COL_RELEASE = 6\nConst COL_SEQUENCE = 7\nConst COL_FAMILY = 8\nConst COL_PATCHXML = 9\nConst COL_PATCHTABLES = 10\nConst COL_REFCNT = 11\nConst COL_APPLIEDCNT = 12\nConst COL_SUPERSEDEDCNT = 13\nConst COL_APPLICABLECNT = 14\nConst COL_NOQALBASELINECNT = 15\nConst COL_PATCHBASELINES = 16\nConst COL_MAX = 16\nConst REG_GLOBALCONFIG = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\&quot;\nConst REG_PRODUCT = &quot;Software\\Classes\\Installer\\&quot;\nConst REG_PRODUCTPERUSER = &quot;Software\\Microsoft\\Installer\\&quot;\nConst REG_PRODUCTPERUSERMANAGED = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\Managed\\&quot;\nConst ERR_REBOOT = &quot;A reboot is required to complete the update(s)!&quot;\n\nConst OFFICE_ALL = &quot;78E1-11D2-B60F-006097C998E7}.0001-11D2-92F2-00104BC947F0}.6000-11D3-8CFE-0050048383C9}.6000-11D3-8CFE-0150048383C9}.BDCA-11D1-B7AE-00C04FB92F3D}.6D54-11D4-BEE3-00C04F990354}.CFDA-404E-8992-6AF153ED1719}.&quot;\n&#039;Office 2000 -&gt; KB230848; Office XP -&gt; KB302663; Office 2003 -&gt; KB832672\nConst OFFICE_2000 = &quot;78E1-11D2-B60F-006097C998E7}&quot;\nConst ORK_2000 = &quot;0001-11D2-92F2-00104BC947F0}&quot;\nConst PRJ_2000 = &quot;BDCA-11D1-B7AE-00C04FB92F3D}&quot;\nConst VIS_2002 = &quot;6D54-11D4-BEE3-00C04F990354}&quot;\nConst OFFICE_2002 = &quot;6000-11D3-8CFE-0050048383C9}&quot;\nConst OFFICE_2003 = &quot;6000-11D3-8CFE-0150048383C9}&quot;\nConst PPS_2007 = &quot;CFDA-404E-8992-6AF153ED1719}&quot; &#039;Project Portfolio Server 2007\nConst OFFICEID = &quot;000-0000000FF1CE}&quot; &#039;cover O12, O14 with 32 &amp; 64 bit\n\nConst DISK_UNKNOWN = 0\nConst DISK_NOROOTDIR = 1\nConst DISK_REMOVABLE = 2\nConst DISK_LOCAL = 3\nConst DISK_NETWORK = 4\nConst DISK_CD = 5\nConst DISK_RAM = 6\n\nConst SQL_CREATEFILETABLE = &quot;CREATE TABLE `File` (`File` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `FileName` CHAR(255) NOT NULL LOCALIZABLE, `FileSize` LONG NOT NULL, `Version` CHAR(72), `Language` CHAR(20), `Attributes` SHORT, `Sequence` LONG NOT NULL PRIMARY KEY `File`)&quot;\nConst SQL_CREATECATABLE = &quot;CREATE TABLE `CustomAction` (`Action` CHAR(72) NOT NULL, `Type` SHORT NOT NULL, `Source` CHAR(72), `Target` CHAR(255) PRIMARY KEY `Action`)&quot;\nConst SQL_CREATEPROPTABLE = &quot;CREATE TABLE `Property` (`Property` CHAR(72) NOT NULL, `Value` LONGCHAR NOT NULL LOCALIZABLE PRIMARY KEY `Property`)&quot;\n\nConst MSIREADSTREAM_ANSI = 2\n\nConst xlTop = &amp;HFFFFEFC0\nConst xlUp = &amp;HFFFFEFBE\nConst xlSrcRange = 1\nConst xlYes = 1\nConst xlNo = 2\nConst xlMaximized = &amp;HFFFFEFD7\n\nConst COL_NONPOUND = 1\nConst COL_POUND = 2\n\nConst HROW = 1\n\nConst SEQ_PATCHFAMILY = 1\nConst SEQ_PRODUCTCODE = 2\nConst SEQ_SEQUENCE = 3\nConst SEQ_ATTRIBUTE = 4\n\nConst MET_COMPANY = 1\nConst MET_PROPERTY = 2\nConst MET_VALUE = 3\n\nConst TARGETGUID = 1\nConst MSPTARGETVER = 2\nConst MSPUPDATEDVER = 3\nConst LCID = 4\nConst CULTURE = 5\nConst VAL_TARGETPRODUCTCODE = 6\nConst VAL_TARGETVERSION = 7\nConst VAL_TARGETLANGUAGE = 8\nConst VAL_TARGETUPGRADECODE = 9\n\nConst OTARGETGUID = 1\nConst OTARGETNAME = 2\nConst OFAMILYVER = 3\nConst OMSPTARGETVER = 4\nConst OMSPUPDATEDVER= 5\nConst OLCID = 6\nConst OCULTURE = 7\nConst OLICENSE = 8\nConst OARCHITECTURE = 9\nConst OVAL_TARGETPRODUCTCODE = 10\nConst OVAL_TARGETVERSION = 11\nConst OVAL_TARGETLANGUAGE = 12\nConst OVAL_TARGETUPGRADECODE = 13\nConst OVAL_REINSTALLMODE = 14\nConst OVAL_REINSTALL = 15\n\nConst F_FILE = 1\nConst F_COMPONENT = 2\nConst F_FILENAME = 3\nConst F_FILESIZE = 4\nConst F_VERSION = 5\nConst F_HASH = 6\nConst F_LANGUAGE = 7\nConst F_ATTRIBUTE = 8\nConst F_SEQUENCE = 9\nConst F_PREDICTED = 10\nConst F_COMPSTATE = 11\nConst F_CURSIZE = 12\nConst F_CURVERSION = 13\nConst F_CURHASH = 14\nConst F_FILEPATH = 15\n\nConst S_PROP = 1\nConst S_VAL = 2\nConst S_ROW_NAME = 2\nConst S_ROW_KB = 3\nConst S_ROW_PACKLET = 4\nConst S_ROW_SEQUENCE = 5\nConst S_ROW_BASELINE = 6\nConst S_ROW_SUPERSEDENCE = 7\nConst S_ROW_UNINSTALLABLE = 8\nConst S_ROW_TITLE = 9\nConst S_ROW_AUTHOR = 10\nConst S_ROW_SUBJECT = 11\nConst S_ROW_COMMENTS = 12\nConst S_ROW_PATCHCODE = 13\nConst S_ROW_TARGETS = 14\nConst S_ROW_OBSOLETES = 15\nConst S_ROW_TRANSFORMSUB = 16\nConst S_ROW_PATCHTYPE = 17\nConst S_ROW_SECURITY = 18\nConst S_ROW_PATCHXML = 19\n\nDim sErr_Syntax\nsErr_Syntax = vbCrLf &amp; _\n&quot;Usage: &quot; &amp; vbTab &amp; SOLUTIONNAME&amp;&quot;.vbs [\/Option] \u2026&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot; \/RepairCache &quot; &amp; vbTab &amp; &quot;Tries to restore missing items in the local WI cache&quot; &amp; vbCrLf &amp; _\n&quot; \/SRestoreLocation=&quot; &amp; vbTab &amp; &quot;A list of fully qualified paths to folders with .msp files, separated by semicolons&quot; &amp; vbCrLf &amp; _\n&quot; &quot; &amp; vbTab &amp; vbTab &amp; &quot;&lt;Folder01&gt;;&lt;\\\\Server02\\Share02&gt;;\u2026&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot; \/ReconcileCache &quot; &amp; &quot;Unregisters missing patches in the cache to unblock broken WI configurations&quot; &amp; vbCrLf &amp; vbCrLf&amp; _\n&quot; \/ApplyPatch &quot; &amp; vbTab &amp; &quot;Apply patches from current folder and SUpdateLocation&quot; &amp; vbCrLf &amp; _\n&quot; \/SUpdateLocation=&quot; &amp; vbTab &amp; &quot;A list of fully qualified paths to folders with .msi and\/or .msp files, separated by semicolons&quot; &amp; vbCrLf &amp; _\n&quot; &quot; &amp; vbTab &amp; vbTab &amp; &quot;&lt;Folder01&gt;;&lt;\\\\Server02\\Share02&gt;;\u2026&quot; &amp; vbCrLf&amp; _\n&quot; \/ExcludeCache&quot;&amp;vbTab&amp; &quot;Will not apply any patches from %windir%\\installer folder&quot; &amp; vbCrLf&amp; _\n&quot; \/IncludeOctCache&quot;&amp;vbTab&amp; &quot;Includes OCT patches from %windir%\\installer folder into patch detection&quot; &amp;vbCrLf&amp;vbCrLf&amp; _\n&quot; \/RemovePatch &quot; &amp; vbTab &amp; &quot;Uninstall specified list of patches, separated by semicolons&quot; &amp; vbCrLf&amp; _\n&quot; &quot; &amp; vbTab &amp; &quot;Accepts KBxxxxxx;{PatchCode};&lt;FullPath&gt;;SUPERSEDED&quot; &amp; vbCrLf&amp;vbCrLf&amp; _\n&quot; \/CleanCache &quot; &amp; vbTab &amp; &quot;Removes unreferenced (orphaned) patch files from the local WI cache&quot; &amp; vbCrLf&amp;vbCrLf&amp; _\n&quot; \/CabExtract=&lt;Patch&gt;&quot; &amp; vbTab &amp; &quot;Extracts the patch embedded .CAB file to the %temp% folder&quot; &amp; vbCrLf&amp;vbCrLf&amp; _\n&quot; \/ViewPatch=&lt;Patch&gt;&quot; &amp; vbTab &amp; &quot;Display the patch contents in Excel&quot; &amp; vbCrLf&amp;vbCrLf&amp; _\n&quot; \/DetectOnly&quot;&amp;vbTab&amp; &quot;Create a log file but do not execute any actions&quot; &amp; vbCrLf&amp;vbCrLf &amp; _\n&quot; \/q &quot; &amp; vbTab&amp; &quot;Suppresses the automatic display of the log file&quot; &amp; vbCrLf&amp;vbCrLf&amp;vbCrLf&amp; _\n&quot; \/register &quot; &amp; vbTab&amp; &quot;Registers OPUtil context menu extensions for .msp files&quot; &amp; vbCrLf&amp;vbCrLf&amp;vbCrLf&amp; _\n&quot; \/unregister&quot; &amp; vbTab&amp; &quot;UnRegisters OPUtil context menu extensions for .msp files&quot; &amp; vbCrLf&amp;vbCrLf&amp;vbCrLf&amp; _\n&quot;By default &#039;RepairCache&#039; and &#039;ApplyPatch&#039; are enabled.&quot; &amp; vbCrLf&amp; _\n&quot;To disable use \/[Option]=False.&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot;Examples&quot; &amp;vbCrLf&amp;&quot;========&quot;&amp;vbCrLf&amp; _\n&quot;Default &#039;RepairCache&#039; &amp; &#039;ApplyPatch&#039; from current directory:&quot; &amp;vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot;Repair and reconcile a broken Windows Installer Cache:&quot; &amp;vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/ReconcileCache \/RepairCache \/SRestoreLocation=&lt;\\\\Location1\\ShareName&gt;;&lt;\\\\Location2\\ShareName&gt;&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot;Create a log for applicability of specific patches:&quot; &amp;vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/ApplyPatch \/SUpdateLocation=&lt;\\\\Location1\\ShareName&gt;;&lt;\\\\Location2\\ShareName&gt; \/ExcludeCache \/DetectOnly&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot;Install applicable patches (including local Windows Installer cache):&quot; &amp;vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/ApplyPatch \/SUpdateLocation=&lt;\\\\Location1\\ShareName&gt;;&lt;\\\\Location2\\ShareName&gt;&quot; &amp; vbCrLf &amp; vbCrLf &amp; _\n&quot;UnInstall patch(es):&quot; &amp;vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/RemovePatch=KB123456;KB654321&quot; &amp; vbCrLf&amp; _ \n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/RemovePatch={PatchCode}&quot; &amp; vbCrLf&amp; _\n&quot; cscript.exe &quot;&amp;SOLUTIONNAME&amp;&quot;.vbs \/RemovePatch=Superseded&quot; &amp; vbCrLf &#039;&amp; vbCrLf &amp; _\n\nDim oMsi,oFso,oReg,oWShell,oShellApp,oWmiLocal,DateTime,XmlDoc,oFile\n\nDim LogStream,ReadStream,LogProd\n\nDim fCScript,fx64,fCleanAggressive,fRebootRequired,fSumInit,fUpdatesCollected,fViewPatch,fCabExtract\nDim fShowLog,fForceRemovePatch,fContextMenu,fDeepScan, fDynSUpdateDiscovered, fMsiProvidedAsFile\nDim fNeedGenericSql\n\nDim sLogFile,sLogSummary,sAppData,sTmp,sTemp,sWinDir,sWICacheDir,sScriptDir,sTimeStamp,sLogNoRef\nDim Location,Key,vWI,vWIMajorMinor,sProductVersionNew,sProductVersionReal,sMspFile,sApplyPatch\nDim sOSinfo, sOSVersion, sComputerName, sExternalMsi\n\nDim iIndex,iVersionNt\n\nDim arrUpdateLocations,arrRestoreLocations,arrSUpdatesAll,arrTmpLog\nDim dicSUpdatesAll,dicFamily,dicSummary,dicRepair,dicLocalDisks,dicMspNoSeq,dicMspMinor,dicMspSmall\nDim dicMspObsoleted,dicMspSequence,dicDynCultFolders,dicSqlCreateTbl,dicProdMst\n\n&#039;=======================================================================================================\n\n&#039;Main\nOn Error Resume Next\n\n&#039;Initialize objects and defaults\nInitialize\n\n&#039;Parse the command line\nParseCmdLine\n\n&#039;Validate ShowLog setting\nIf (fApplyPatch AND NOT fViewPatch) OR fCleanCache OR fForceRemovePatch OR fReconcileCache OR fRemovePatch OR fRepairCache Then fShowLog = True\n\nsTmp = &quot;&quot;\nLog &quot;Current Settings:&quot;\nLog Space(30)&amp;&quot;\/RepairCache &quot;&amp;fRepairCache&amp;&quot;, &quot;&amp;&quot;\/SRestoreLocation=&quot;&amp;sRestoreLocation\nLog Space(30)&amp;&quot;\/ReconcileCache &quot;&amp;fReconcileCache\nLog Space(30)&amp;&quot;\/ApplyPatch &quot;&amp;fApplyPatch&amp;&quot;, \/ExcludeCache &quot;&amp;fExcludeCache&amp;&quot;, \/IncludeOctCache &quot;&amp;fIncludeOctCache&amp;&quot;, \/SUpdateLocation=&quot;&amp;sUpdateLocation\nLog Space(30)&amp;&quot;\/RemovePatch &quot;&amp;fRemovePatch&amp;&quot;, Patches=&quot;&amp;sMspRemoveFilter\nLog Space(30)&amp;&quot;\/CleanCache &quot;&amp;fCleanCache\nLog Space(30)&amp;&quot;\/CabExtract &quot;&amp;fCabExtract\nLog Space(30)&amp;&quot;\/ViewPatch &quot;&amp;fViewPatch\nLog Space(30)&amp;&quot;\/DetectOnly &quot;&amp;fDetectOnly\nLog Space(30)&amp;&quot;\/Q &quot;&amp;bQuiet &amp;vbCrLf\nLog Space(30)&amp;&quot;For more details on available commands run &quot;&amp;chr(34)&amp;&quot;cscript OPUtil.vbs \/?&quot;&amp;chr(34)&amp;vbCrLf\n\nIf Not Err = 0 Then\nLog &quot;Error: Could not determine script parameters. Aborting&quot;\nLog vbCrLf &amp; &quot;End of script: &quot; &amp; Now\nLogStream.Close\nwscript.Quit 1\nEnd If\n\nIf fCscript Then wscript.echo &quot;Init complete&quot;\n\n&#039;Log if DetectOnly\nIf fDetectOnly Then\nsTmp = &quot;DetectOnly mode. No changes will be done to the system!&quot;\nLog String(Len(sTmp),&quot;=&quot;)&amp;vbCrLf&amp;sTmp&amp;vbCrLf&amp;String(Len(sTmp),&quot;=&quot;)&amp;vbCrLf\nsTmp = &quot;&quot;\nEnd If &#039;fDetectOnly\n\n&#039;Set the bookmark for the summary\nLog &quot;[SUMMARY]&quot;\n\n&#039;Add marker to indicate the start of debug logging section\nsTmp = &quot;Debug Logging Section&quot;\nLog vbCrLf&amp;String(Len(sTmp),&quot;=&quot;)&amp;vbCrLf&amp;sTmp&amp;vbCrLf&amp;String(Len(sTmp),&quot;=&quot;)\n\n&#039;Ensure correct value for SRestoreLocation\narrRestoreLocations = EnsureLocation(sRestoreLocation &amp; &quot;;&quot; &amp; sUpdateLocation)\n\n&#039;Ensure correct value for SUpdateLocation\nIf (NOT fApplyPatch) AND (NOT fRemovePatch) AND sUpdateLocation=&quot;&quot; _\nThen arrUpdateLocations=Split(sScriptDir,&quot;;&quot;) _\nElse arrUpdateLocations = EnsureLocation(sUpdateLocation)\n\n&#039;&quot;RepairCache&quot; .msi\/.msp resiliency&#039;\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nIf fRepairCache Then\n&#039;Build the &#039;Repair&#039; references\nInitRepairDic\n&#039;Check and try to restore missing files if needed\nRepairCache\nEnd If &#039;fRepairCache\n\n&#039;&quot;ReconcileCache&quot; patch reconcile\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nIf fReconcileCache Then MspReconcile\n\n&#039;&quot;ApplyPatch&quot; apply patches\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nIf fApplyPatch AND NOT fViewPatch Then ApplyPatches\n\n&#039;&quot;CleanCache&quot; orphaned .msp cleanup\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nIf fCleanCache Then WICleanOrphans\n\n&#039;&quot;RemovePatch&quot; Uninstall patches\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nIf fRemovePatch Then \nDim arrMspRemove\nDim Msp,Item\n\nsTmp = &quot;Running MspRemove with filter: &#039;&quot; &amp; sMspRemoveFilter &amp;&quot;&#039;&quot;\nLog vbCrLf&amp;vbCrLf&amp;sTmp &amp;vbCrLf&amp; String(Len(sTmp),&quot;-&quot;)\nIf fCscript Then wscript.echo &quot;Checking for removable patches&quot;\n\narrMspRemove = Split(sMspRemoveFilter,&quot;;&quot;)\nFor Each Item In arrMspRemove\nMsp = Item\n&#039;Check if it&#039;s a reference to a .msp file\nIf Len(Msp)&gt;4 Then\nIf LCase(Right(Msp,4))=&quot;.msp&quot; Then\nIf oFso.FileExists(Msp) Then\nsMspRemoveFilter=sMspRemoveFilter &amp; Left(oMsi.SummaryInformation(Msp).Property(PID_REVNUMBER),38)\nEnd If\nEnd If\nEnd If\nIf NOT Left(Msp,1)=&quot;{&quot; Then\nIf NOT fUpdatesCollected Then CollectSUpdates\nFor iIndex = 0 To UBound(arrSUpdatesAll)\nIf arrSUpdatesAll(iIndex,COL_KB) = Replace(Msp,&quot;KB&quot;,&quot;&quot;) Then sMspRemoveFilter=sMspRemoveFilter &amp; &quot;;&quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)\nIf UCase(arrSUpdatesAll(iIndex,COL_RELEASE)) = UCase(Msp) Then sMspRemoveFilter=sMspRemoveFilter &amp; &quot;;&quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)\nNext &#039;iIndex\nEnd If\nNext &#039;Item\nIf InStr(sMspRemoveFilter,&quot;{&quot;)&gt;0 Then MspRemove sMspRemoveFilter,sMspProductFilter\nIf InStr(UCase(sMspRemoveFilter),&quot;SUPERSEDED&quot;)&gt;0 Then MspRemove &quot;Superseded&quot;,sMspProductFilter\nEnd If\n\n&#039;&quot;ViewPatch&quot;\n&#039;\u2014\u2014\u2014-\nIf fViewPatch Then\nViewPatch sMspFile\nEnd If\n\n&#039;&quot;CabExtract&quot;\n&#039;\u2014\u2014\u2014\u2014-\nIf fCabExtract Then\nsTmp = CabExtract(sMspFile)\nEnd If\n\n \n\n&#039;Check reboot requirement\nIf fRebootRequired Then\nLog vbCrLf &amp; &quot;Note: &quot;&amp;ERR_REBOOT\nwscript.echo ERR_REBOOT\nEnd If &#039;fRebootRequired\n\n&#039;Close the temp log\nsTmp = &quot;End of script: &quot; &amp; Now\nLog vbCrLf &amp; vbCrLf &amp; String(Len(sTmp),&quot;=&quot;) &amp;vbCrLf &amp; sTmp\nLogStream.Close\n&#039;Create the final log including the summary section\n\n&#039;Log Notes &amp; Errors\nFor Each Key in dicSummary.Keys\nsTmp = &quot;&quot;\nIf NOT Left(Key,1)=&quot;{&quot; Then sLogSummary = sLogSummary &amp;vbCrLf&amp;Key &amp; dicSummary.Item(Key)\nNext &#039;Key\n\n&#039;By Patch Summary \nfSumInit = False\nIf fApplyPatch AND (NOT fViewPatch OR fDeepScan) Then\nFor iIndex = 0 To UBound(arrSUpdatesAll)\nIf (NOT Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir)) = sWICacheDir) OR _\n(Len(arrSUpdatesAll(iIndex,COL_APPLICABLECNT))&gt;0) OR _\n(Len(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT))&gt;0) Then\n&#039;Found patch to be logged\n&#039;Check if Heading needs to be added\nIf NOT fSumInit Then\nfSumInit = True\nsLogSummary = sLogSummary &amp;vbCrLf&amp;vbCrLf&amp;&quot;Summary By Patch&quot;&amp;vbCrLf&amp;&quot;================&quot;\nsLogNoRef = &quot;&quot;\nEnd If\n&#039;Add patch &amp; product details\nIf (arrSUpdatesAll(iIndex,COL_REFCNT)= 0) Then\nsLogNoRef = sLogNoRef &amp;vbCrLf&amp;&quot; - KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp; _\n&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nElse\nsLogSummary = sLogSummary &amp;vbCrLf&amp;vbCrLf&amp;&quot;KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp; _\n&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\n&#039;Applied details\nsTmp = vbTab&amp;&quot;Applied: &quot;\nIf InStr(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),&quot;;&quot;)&gt;0 Then\nReDim arrTmpLog(-1)\nIf Right(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),1)=&quot;;&quot; Then _\narrSUpdatesAll(iIndex,COL_APPLIEDCNT) = Left(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),Len(arrSUpdatesAll(iIndex,COL_APPLIEDCNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),&quot;;&quot;)\nsTmp = sTmp &amp; &quot;Patch is installed to &quot;&amp;UBound(arrTmpLog)+1&amp;&quot; product(s)&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;vbCrLf&amp;vbTab&amp;vbTab&amp;LogProd&amp;&quot; - &quot;\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)\nNext &#039;LogPatch\n\nsLogSummary = sLogSummary &amp;vbCrLf&amp;sTmp\nElse\nsTmp = sTmp &amp; &quot;No&quot;\nEnd If\n\n&#039;Superseded details\nsTmp = vbTab&amp;&quot;Superseded: &quot;\nIf InStr(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),&quot;;&quot;)&gt;0 Then\nReDim arrTmpLog(-1)\nIf Right(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),1)=&quot;;&quot; Then _\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = Left(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),Len(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),&quot;;&quot;)\nsTmp = sTmp &amp; &quot;Patch is superseded for &quot;&amp;UBound(arrTmpLog)+1&amp;&quot; product(s)&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;vbCrLf&amp;vbTab&amp;vbTab&amp;LogProd&amp;&quot; - &quot;\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)\nNext &#039;LogPatch\nsLogSummary = sLogSummary &amp;vbCrLf&amp;sTmp\nElse\nsTmp = sTmp &amp; &quot;No&quot;\nEnd If\n\n&#039;Applicable details\nsTmp = vbTab&amp;&quot;Applicable: &quot;\nIf InStr(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),&quot;;&quot;)&gt;0 Then\nReDim arrTmpLog(-1)\nIf Right(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),1)=&quot;;&quot; Then _\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = Left(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),Len(arrSUpdatesAll(iIndex,COL_APPLICABLECNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),&quot;;&quot;)\nsTmp = sTmp &amp; &quot;Patch is applicable to &quot;&amp;UBound(arrTmpLog)+1&amp;&quot; product(s)&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;vbCrLf&amp;vbTab&amp;vbTab&amp;LogProd&amp;&quot; - &quot;\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)\nNext &#039;LogPatch\nsLogSummary = sLogSummary &amp;vbCrLf&amp;sTmp\nElse\nsTmp = sTmp &amp; &quot;No&quot;\nEnd If\n\n&#039;Applicable but no valid baseline details\nsTmp = vbTab&amp;&quot;Can&#039;t apply: &quot;\nIf InStr(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),&quot;;&quot;)&gt;0 Then\nReDim arrTmpLog(-1)\nIf Right(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),1)=&quot;;&quot; Then _\narrSUpdatesAll(iIndex,COL_NOQALBASELINECNT) = Left(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),Len(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),&quot;;&quot;)\nsTmp = sTmp &amp; &quot;Patch is applicable to &quot;&amp;UBound(arrTmpLog)+1&amp;&quot; product(s) but the product(s) do(es) not meet the required SP level &quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;vbCrLf&amp;vbTab&amp;vbTab&amp;LogProd&amp;&quot; - &quot;&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch baseline(s): &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHBASELINES)&amp; &quot;. Installed baseline: &quot; &amp;oMsi.ProductInfo(LogProd,&quot;VersionString&quot;)&amp;vbCrLf\nNext &#039;LogPatch\nsLogSummary = sLogSummary &amp;vbCrLf&amp;sTmp\nElse\nsTmp = sTmp &amp; &quot;No&quot;\nEnd If\n\nEnd If \nEnd If\nNext &#039;iIndex\nIf NOT sLogNoRef=&quot;&quot; Then sLogSummary = sLogSummary &amp; vbCrLf&amp;vbCrLf&amp; &quot;Patch(es) that don&#039;t target any installed applications:&quot;&amp; sLogNoRef\nEnd If &#039;fApplyPatch\n\n&#039;By Product Summary\nIf fSumInit Then sLogSummary = sLogSummary &amp;vbCrLf&amp;vbCrLf&amp;vbCrLf&amp;&quot;Summary By Product&quot;&amp;vbCrLf&amp;&quot;==================&quot;&amp;vbCrLf\n\nFor Each Key in dicSummary.Keys\nsTmp = &quot;&quot;\nIf Left(Key,1)=&quot;{&quot; Then \nsTmp = Key&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Key,&quot;ProductName&quot;) \nsLogSummary = sLogSummary &amp;vbCrLf&amp;sTmp &amp; dicSummary.Item(Key)\nEnd If\nNext &#039;Key\nErr.Clear\nIf sLogSummary = &quot;===============&quot;&amp;vbCrLf&amp;&quot;Summary Section&quot;&amp;vbCrLf&amp;&quot;===============&quot;&amp;vbCrLf Then sLogSummary = sLogSummary&amp;vbCrLf&amp;&quot; All appears to be well.&quot;&amp;vbCrLf&amp;vbCrLf&amp;&quot;For detailed logging see the &#039;Debug&#039; section below.&quot;&amp;vbCrLf\nSet ReadStream= oFso.OpenTextFile(sLogFile,FOR_READING,False,TRISTATE_USEDEFAULT)\nSet LogStream = oFso.CreateTextFile(sTemp&amp;SOLUTIONNAME&amp;&quot;.log&quot;,True,True)\nDo While Not ReadStream.AtEndOfStream\nsTmp = ReadStream.ReadLine\nIf NOT InStr(sTmp,&quot;[SUMMARY]&quot;)&gt;0 Then\nLogStream.WriteLine sTmp\nElse\nLogStream.Write sLogSummary&amp;vbCrLf\nIf fRebootRequired Then LogStream.Write vbCrLf&amp;ERR_REBOOT&amp;vbCrLf&amp;vbCrLf\nIf (NOT ReadStream.AtEndOfStream AND NOT fNoDebugLog) Then LogStream.Write ReadStream.ReadAll\nExit Do\nEnd If\nLoop\nReadStream.Close\nLogStream.Close\noFso.DeleteFile sLogFile\n\n&#039;Show completion notice\nIf fCscript Then wscript.echo &quot;Script execution complete.&quot;\n\n&#039;Show the log\nIf (NOT bQuiet) AND fShowLog Then oWShell.Run chr(34) &amp; sTemp&amp;SOLUTIONNAME&amp;&quot;.log&quot; &amp; chr(34)\n\n&#039;END \n&#039;====\n&#039;=======================================================================================================\n\n&#039;Initialize Objects and defaults\nSub Initialize\n\nDim Item,ComputerItem,Process,Processes\n\nDim iInstanceCnt\n\nOn Error Resume Next\n\nfCScript = False\nfx64 = False\nfViewPatch = False\nfCabExtract = False\nfShowLog = False\nsMspFile = &quot;&quot;\nsApplyPatch = &quot;&quot;\nfUpdatesCollected = False\nfCleanAggressive = False\nfForceRemovePatch = False\nfRebootRequired = False\nfContextMenu = False\nfDeepScan = False\nfDynSUpdateDiscovered = False\nfNeedGenericSql = True\nfMsiProvidedAsFile = False\n\nSet dicSqlCreateTbl = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicProdMst = CreateObject(&quot;Scripting.Dictionary&quot;)\n\nsLogSummary = &quot;===============&quot;&amp;vbCrLf&amp;&quot;Summary Section&quot;&amp;vbCrLf&amp;&quot;===============&quot;&amp;vbCrLf\n\nSet oMsi = CreateObject(&quot;WindowsInstaller.Installer&quot;)\nSet oFso = CreateObject(&quot;Scripting.FileSystemObject&quot;)\nSet oWShell = CreateObject(&quot;WScript.Shell&quot;)\nSet oShellApp = CreateObject(&quot;Shell.Application&quot;)\nSet oReg = GetObject(&quot;winmgmts:\\\\.\\root\\default:StdRegProv&quot;)\nSet oWmiLocal = GetObject(&quot;winmgmts:\\\\.\\root\\cimv2&quot;)\nSet DateTime = CreateObject(&quot;WbemScripting.SWbemDateTime&quot;)\nSet XmlDoc = CreateObject(&quot;Microsoft.XMLDOM&quot;)\n\n&#039;Ensure there&#039;s only a single instance running of this script\niInstanceCnt = 0\nSet Processes = oWmiLocal.ExecQuery(&quot;Select * From Win32_Process&quot;)\nFor Each Process in Processes\nIf LCase(Mid(Process.Name,2,6))=&quot;script&quot; Then \nIf InStr(LCase(Process.CommandLine),&quot;oputil&quot;)&gt;0 Then iInstanceCnt=iInstanceCnt+1\nEnd If\nNext &#039;Process\nIf iInstanceCnt&gt;1 Then\nwscript.echo &quot;Error: Another instance of this script is already running.&quot;\nwscript.quit\nEnd If\n\n&#039;Obtain the current timestamp\nDateTime.SetVarDate Now,True\nsTimeStamp = Left(DateTime.Value,14)\n\n&#039;Are we running on Cscript?\nfCScript = (LCase(Mid(Wscript.FullName, Len(Wscript.Path)+2, 1)) = &quot;c&quot;)\n\n&#039;Get environment path info\nsAppData = oWShell.ExpandEnvironmentStrings(&quot;%appdata%&quot;)&amp;&quot;\\&quot;\nsTemp = oWShell.ExpandEnvironmentStrings(&quot;%temp%&quot;)&amp;&quot;\\&quot;\nsWinDir = oWShell.ExpandEnvironmentStrings(&quot;%windir%&quot;)&amp;&quot;\\&quot;\nsWICacheDir = sWinDir&amp;&quot;Installer\\&quot;\nsScriptDir = wscript.ScriptFullName\nsScriptDir = Left(sScriptDir,InStrRev(sScriptDir,&quot;\\&quot;))\n\n&#039;Init default for the resiliency .msi &amp; .msp location\nsRestoreLocation = sRestoreLocation&amp;&quot;;&quot;&amp;sScriptDir&amp;&quot;;&quot;&amp;sWICacheDir\nIf Left(sRestoreLocation,1)=&quot;;&quot; Then sRestoreLocation = Mid(sRestoreLocation,2)\n\n&#039;Create the logfile with initial data\nsLogFile = sTemp &amp; &quot;~&quot;&amp;SOLUTIONNAME&amp;&quot;.log&quot;\nSet LogStream = oFso.CreateTextFile(sLogFile,True,True)\nLog &quot;Microsoft Customer Support Services - &quot; &amp;SOLUTIONNAME &amp; &quot; V &quot;&amp; SCRIPTBUILD &amp; &quot; - &quot; &amp; Now &amp; vbCrLf\nvWI = oMsi.Version\nvWIMajorMinor = Left(vWi,3)\nsTmp = &quot;Windows Installer Version:&quot;\nLog sTmp&amp;Space(30-Len(sTmp))&amp;vWI\nsTmp = &quot;ComputerName:&quot;\nLog sTmp&amp;Space(30-Len(sTmp))&amp; oWShell.ExpandEnvironmentStrings(&quot;%COMPUTERNAME%&quot;)\n\n&#039;Initialize the &#039;Summary&#039; log dictionary\nSet dicSummary = CreateObject(&quot;Scripting.Dictionary&quot;)\n\nIf vWIMajorMinor = &quot;4.5&quot; AND vWI&lt;&quot;4.5.6001.22392&quot; Then _\nLogSummary &quot;Important Note:&quot;,&quot;KB 972397 contains important updates for the installed version of Windows Installer! http:\/\/support.microsoft.com\/kb\/972397\/EN-US\/&#8221;&quot;\n&#039;More recent version of WI: KB 2388997. KB search term &quot;Msi.dll&quot; AND &quot;hotfix information&quot;\n\n&#039;Detect if we&#039;re running on a 64 bit OS\nSet ComputerItem = oWmiLocal.ExecQuery(&quot;Select * from Win32_ComputerSystem&quot;)\nFor Each Item In ComputerItem\nfx64 = Instr(Left(Item.SystemType,3),&quot;64&quot;) &gt; 0\nsTmp = &quot;OS Architecture:&quot;\nLog sTmp&amp;Space(30-Len(sTmp))&amp; Item.SystemType\nNext\n\n&#039;Find local disk drives\nSet dicLocalDisks = CreateObject(&quot;Scripting.Dictionary&quot;)\nFindLocalDisks\n\nEnd Sub\n&#039;=======================================================================================================\n\n&#039;Command Line Parser\nSub ParseCmdLine\n\nDim sTmp,sArg\n\nDim iCnt,iArgCnt\n\nDim fIgnoreOnce,fArray,fValidCmdFound\n\nDim arrArg\n\nOn Error Resume Next\n\nfIgnoreOnce = False\nfArray = False\niArgCnt = WScript.Arguments.Count\nIf Not iArgCnt &gt; 0 Then \n&#039;Use defaults\nExit Sub\nEnd If\n\nDim iActionCnt\nDim fRepairCacheOrg,fReconcileCacheOrg,fApplyPatchOrg,fRemovePatchOrg,fCleanCacheOrg\n\n&#039;Found command line argument(s) -&gt; default to disabled modules\niActionCnt = 0\nfValidCmdFound = False\nfRepairCacheOrg = fRepairCache : fRepairCache = False\nfReconcileCacheOrg = fReconcileCache : fReconcileCache = False\nfApplyPatchOrg = fApplyPatch : fApplyPatch = False\nfRemovePatchOrg = fRemovePatch : fRemovePatch = False\nfCleanCacheOrg = fCleanCache : fCleanCache = False\n\nFor iCnt = 0 To (iArgCnt-1)\n\nsArg = &quot;&quot;\nsArg = UCase(WScript.Arguments(iCnt))\nIf InStr(sArg,&quot;=&quot;)&gt;0 Then\nSet arrArg = Nothing\narrArg=Split(sArg,&quot;=&quot;,2)\nsArg = arrArg(0)\nfArray = True\nEnd If\n\nSelect Case sArg\n\nCase &quot;\/?&quot;\nWscript.Echo sErr_Syntax\nWscript.Quit\nCase &quot;\/APPLYPATCH&quot;,&quot;-APPLYPATCH&quot;,&quot;APPLYPATCH&quot;\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then\nfApplyPatch = False\nfApplyPatchOrg = False\nfValidCmdFound = False\nEnd If\nfApplyPatch = True\niActionCnt = iActionCnt + 1\nsApplyPatch = arrArg(1)\nIf oFso.FileExists(sApplyPatch) Then\nSet oFile = oFso.GetFile(sApplyPatch)\nIf sUpdateLocation = &quot;&quot; Then sUpdateLocation = oFile.ParentFolder.Path Else sUpdateLocation = sUpdateLocation&amp;&quot;;&quot;&amp;oFile.ParentFolder.Path\nEnd If\nfArray = False\nElse\nfApplyPatch = True\niActionCnt = iActionCnt + 1\nEnd If\n\nCase &quot;\/CABEXTRACT&quot;,&quot;-CABEXTRACT&quot;,&quot;CABEXTRACT&quot;\nfValidCmdFound = True\niActionCnt = iActionCnt + 1\nfCabExtract = True\nIf fArray Then\nsMspFile = arrArg(1)\nfArray = False\nElse\nfIgnoreOnce = True\nIf (iArgCnt-1) &gt; iCnt Then _\nsMspFile = WScript.Arguments(iCnt+1) Else\nsMspFile = &quot;&quot;\nEnd If\n\nCase &quot;\/CLEANAGGRESSIVE&quot;,&quot;-CLEANAGGRESSIVE&quot;,&quot;CLEANAGGRESSIVE&quot;\nfCleanAggressive = True\n\nCase &quot;\/CLEANCACHE&quot;,&quot;-CLEANCACHE&quot;,&quot;CLEANCACHE&quot;\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then\nfCleanCache = False\nfCleanCacheOrg = False\nEnd If\nIf arrArg(1) = &quot;TRUE&quot; Then\nfCleanCache = True\niActionCnt = iActionCnt + 1\nEnd If\nfArray = False\nElse\nfCleanCache = True\niActionCnt = iActionCnt + 1\nEnd If\n\nCase &quot;\/CONTEXTMENU&quot;\nfContextMenu = True\n\n&#039; Case &quot;\/DEEPSCAN&quot;\n&#039; iActionCnt = iActionCnt + 1\n&#039; fDeepScan = True\n&#039; fDetectOnly = True\n&#039; fApplyPatch = True\n\nCase &quot;\/DETECTONLY&quot;,&quot;-DETECTONLY&quot;,&quot;DETECTONLY&quot;\nIf fArray Then\nIf arrArg(1)=&quot;TRUE&quot; OR arrArg(1)=1 Then fDetectOnly=True Else fDetectOnly=False\nfArray=False\nElse\nfDetectOnly = True\nEnd If &#039;fArray\n\nCase &quot;\/DISABLEREPAIR&quot;,&quot;-DISABLEREPAIR&quot;,&quot;DISABLEREPAIR&quot;\nfRepairCache = False\n\nCase &quot;\/EXCLUDECACHE&quot;,&quot;-EXCLUDECACHE&quot;,&quot;EXCLUDECACHE&quot;\nfExcludeCache = True\n\n&#039;Warning: This is an undocumented and unsupported feature!\nCase &quot;\/FORCEREMOVEPATCH&quot;,&quot;-FORCEREMOVEPATCH&quot;,&quot;FORCEREMOVEPATCH&quot;\nfForceRemovePatch = True\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then \nfForceRemovePatch = False\nElse\nfRemovePatch = True\nsMspRemoveFilter = Replace(arrArg(1),&quot;,&quot;,&quot;;&quot;)\niActionCnt = iActionCnt + 1\nEnd If\nfArray = False\nEnd If\n\nCase &quot;\/INCLUDEOCTCACHE&quot;,&quot;-INCLUDEOCTCACHE&quot;,&quot;INCLUDEOCTCACHE&quot;\nfIncludeOctCache = True\n\nCase &quot;\/R&quot;,&quot;-R&quot;,&quot;R&quot;,&quot;\/REGISTER&quot;,&quot;-REGISTER&quot;,&quot;REGISTER&quot;\nfValidCmdFound = True\niActionCnt = iActionCnt + 1\nRegisterShellExt\n\nCase &quot;\/RECONCILECACHE&quot;,&quot;-RECONCILECACHE&quot;,&quot;RECONCILECACHE&quot;\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then \nfReconcileCache = False\nfReconcileCacheOrg = False\nEnd If\nIf arrArg(1) = &quot;TRUE&quot; Then\nfReconcileCache = True\niActionCnt = iActionCnt + 1\nEnd If\nfArray = False\nElse\nfReconcileCache = True\niActionCnt = iActionCnt + 1\nEnd If\n\nCase &quot;\/REMOVEPATCH&quot;,&quot;-REMOVEPATCH&quot;,&quot;REMOVEPATCH&quot;\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then \nfRemovePatch = False\nfRemovePatchOrg = False\nElse\nfRemovePatch = True\nsMspRemoveFilter = Replace(arrArg(1),&quot;,&quot;,&quot;;&quot;)\niActionCnt = iActionCnt + 1\nEnd If\nfArray = False\nElse\nfRemovePatch = True\niActionCnt = iActionCnt + 1\nEnd If\n\nCase &quot;\/REPAIRCACHE&quot;,&quot;-REPAIRCACHE&quot;,&quot;REPAIRCACHE&quot;\nfValidCmdFound = True\nfShowLog = True\nIf fArray Then\nIf arrArg(1) = &quot;FALSE&quot; Then \nfRepairCache = False\nfRepairCacheOrg = False\nEnd If\nIf arrArg(1) = &quot;TRUE&quot; Then\nfRepairCache = True\niActionCnt = iActionCnt + 1\nEnd If\nfArray = False\nElse\nfRepairCache = True\niActionCnt = iActionCnt + 1\nEnd If\n\nCase &quot;\/SRESTORELOCATION&quot;,&quot;-SRESTORELOCATION&quot;,&quot;SRESTORELOCATION&quot; &#039;SRestoreLocation\nIf fArray Then\nsRestoreLocation = arrArg(1)\nfArray = False\nElse\nfIgnoreOnce = True\nsRestoreLocation = WScript.Arguments(iCnt+1)\nEnd If\n\nCase &quot;\/SUPDATELOCATION&quot;,&quot;-SUPDATELOCATION&quot;,&quot;SUPDATELOCATION&quot;,&quot;\/SUPDATESLOCATION&quot;,&quot;-SUPDATESLOCATION&quot;,&quot;SUPDATESLOCATION&quot; &#039;SUpdateLocation\nIf fArray Then\nsUpdateLocation = arrArg(1)\nfArray = False\nElse\nfIgnoreOnce = True\nsUpdateLocation = WScript.Arguments(iCnt+1)\nEnd If\n\nCase &quot;\/U&quot;,&quot;-U&quot;,&quot;U&quot;,&quot;\/UNREGISTER&quot;,&quot;UNREGISTER&quot;,&quot;UNREGISTER&quot;\nfValidCmdFound = True\niActionCnt = iActionCnt + 1\nUnRegisterShellExt\n\nCase &quot;\/VIEW&quot;,&quot;\/VIEWPATCH&quot;\niActionCnt = iActionCnt + 1\nfValidCmdFound = True\nfViewPatch = True\nfDetectOnly = True\nfApplyPatch = True\nfDeepScan = True\nIf fArray Then\nsMspFile = arrArg(1)\nIf oFso.FileExists(sMspFile) Then\nSet oFile = oFso.GetFile(sMspFile)\nIf sUpdateLocation = &quot;&quot; Then sUpdateLocation = oFile.ParentFolder.Path Else sUpdateLocation = sUpdateLocation&amp;&quot;;&quot;&amp;oFile.ParentFolder.Path\nEnd If\nfArray = False\nElse\nfIgnoreOnce = True\nIf (iArgCnt-1) &gt; iCnt Then _\nsMspFile = WScript.Arguments(iCnt+1) Else _\nsMspFile = &quot;&quot;\nEnd If\n\nCase &quot;\/Q&quot;\nbQuiet = True\n\nCase Else\nIf NOT fIgnoreOnce Then\nsTmp = &quot;&quot;\nsTmp = vbCrLf&amp;&quot;Warning: Invalid command line switch &#039;&quot; &amp; WScript.Arguments(iCnt) &amp; &quot;&#039; will be ignored.&quot;&amp;vbCrLf\nIf NOT bQuiet Then wscript.echo sTmp\nLog sTmp\nfIgnoreOnce = NOT fIgnoreOnce\nEnd If\n\nEnd Select\n\nNext &#039;iCnt\n\n&#039;Ensure we had a valid Cmd\nIf NOT fValidCmdFound OR iActionCnt = 0 Then\n&#039;Restore defaults\nfShowLog = True\nfRepairCache = fRepairCacheOrg\nfReconcileCache = fReconcileCacheOrg\nfApplyPatch = fApplyPatchOrg\nfRemovePatch = fRemovePatchOrg\nfCleanCache = fCleanCacheOrg\nEnd If\n\nEnd Sub &#039;ParseCmdLine\n&#039;=======================================================================================================\n\n&#039;Build a dictionary array for a reference list of available .msi and .msp packages\n&#039;Key for .msi is &lt;ProductCode&gt;_&lt;PackageCode&gt; (required to support AIP installs)\n&#039;Key for .msp is &lt;PatchCode&gt;\nSub InitRepairDic\n\nDim File,Folder,MspDb,Record,SumInfo\n\nDim sProductCode,sPackageCode,sKey\n\nDim qView\n\nOn Error Resume Next\n\nIf fCscript Then wscript.echo &quot;Collecting resiliency data&quot;\nSet dicRepair = CreateObject(&quot;Scripting.Dictionary&quot;)\n\nFor Each Folder in arrRestoreLocations\nIf fCscript Then wscript.echo vbTab&amp;&quot;Collect files from &quot;&amp;Folder\nFor Each File in oFso.GetFolder(Folder).Files\nSelect Case LCase(Right(File.Name,4))\nCase &quot;.msp&quot;\nsKey = &quot;&quot;\nsKey = oMsi.SummaryInformation(File.Path,MSIOPENDATABASEMODE_READONLY).Property(PID_REVNUMBER)\nIf Len(sKey)&gt;LEN_GUID Then sKey=Left(sKey,LEN_GUID)\nIf Not dicRepair.Exists(sKey) Then dicRepair.Add sKey,File.Path\nCase &quot;.msi&quot;\nsKey = GetMsiProductCode(File.Path)&amp;&quot;_&quot;&amp;GetMsiPackageCode(File.Path)\nIf Not dicRepair.Exists(sKey) Then dicRepair.Add sKey,File.Path\nCase Else\n&#039;Do Nothing\nEnd Select\nNext &#039;File\nNext &#039;Folder\n\nEnd Sub &#039;InitRepairDic\n&#039;=======================================================================================================\n\nSub RepairCache\n\nDim File,Product,Prod,PatchList,Patch,Source,MsiSources,MspSources\n\nDim sLocalMsi,sLocalMsp,sKey,sSourceKey,sRepair,sFile,sFileName,sPackage\nDim sRegPackageCode,sMsiPackageCode,sGlobalPatchesKey,sClassesPatchesKey\n\nDim fTrySource,fRepaired,fMsiOK,fMsiRename,fReLoop\n\nDim dicMspError, dicMspChecked, dicMspUnreg ,arrKeys\n\nDim iReLoop\n\nOn Error Resume Next\n\nSet dicMspError = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspChecked = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspUnreg = CreateObject(&quot;Scripting.Dictionary&quot;)\n\nsTmp = &quot;Running RepairCache&quot;\nLog vbCrLf&amp;vbCrLf&amp;sTmp&amp;vbCrLf&amp; String(Len(sTmp),&quot;-&quot;)\nIf fCscript Then wscript.echo &quot;Scanning Windows Installer cache&quot;\n\nFor Each Product in oMsi.Products\nLog vbCrLf&amp;&quot;Product: &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;)\nIf fCscript Then wscript.echo vbTab&amp;&quot;Scan &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;)\n\n&#039;Check local .msi package\nsLocalMsi = &quot;&quot; : sRegPackageCode = &quot;&quot; : sFileName = &quot;&quot; : sRepair = &quot;&quot;\nfTrySource = False : fRepaired = False : fMsiOK = False : fMsiRename = False\nErr.Clear\nsLocalMsi = oMsi.ProductInfo(Product,&quot;LocalPackage&quot;)\nsRegPackageCode = oMsi.ProductInfo(Product,&quot;PackageCode&quot;)\nIf Err = 0 Then\nIf oFso.FileExists(sLocalMsi) Then\nsMsiPackageCode = GetMsiPackageCode(sLocalMsi)\nIf sRegPackageCode = sMsiPackageCode Then \nfMsiOK = True\nLog vbTab&amp;&quot;Success: Local .msi package &quot; &amp;sLocalMsi &amp; &quot; is available and valid.&quot;\nElse\n&#039;PackageCode mismatch! Windows Installer will not accept the local copy in the WI cache as valid.\nfMsiRename = True\nsTmp = vbTab&amp;&quot;Error: Local .msi package &quot;&amp;sLocalMsi&amp;&quot; is available but invalid. Registered PackageCode &#039;&quot;&amp;sRegPackageCode&amp;&quot;&#039; does not match cached files PackageCode &#039;&quot;&amp;sMsiPackageCode&amp;&quot;&#039;&quot;\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;&quot;Error: Local .msi package &quot;&amp;sLocalMsi&amp;&quot; is available but invalid.&quot;\nEnd If\nEnd If &#039;oFso.FileExists\nElse\nErr.Clear\nEnd If\n\nIf NOT fMsiOK Then\nIf sLocalMsi = &quot;&quot; Then \nsTmp = vbTab&amp;&quot;Error: No local .msi package registered. Cannot restore.&quot;\nLog sTmp\nElse\n&#039;Try to restore from available resources\nsRepair = &quot;Error: Local .msi package missing. Attempt failed to restore &quot;\nIf fMsiRename Then sRepair = &quot;Note: No matching .msi package available to replace the mismatched file &quot;\nsKey = &quot;&quot;\nsKey = Product&amp;&quot;_&quot;&amp;sRegPackageCode\nIf dicRepair.Exists(sKey) Then \nIf fMsiRename Then\nIf NOT fDetectOnly Then\nsFileName = &quot;&quot;\nSet File = oFso.GetFile(sLocalMsi)\nsFileName = File.Name\nFile.Name = &quot;Renamed_&quot;&amp;File.Name\noFso.CopyFile dicRepair.Item(sKey),sLocalMsi\nEnd If &#039;fDetectOnly\nElse\nIf NOT fDetectOnly Then oFso.CopyFile dicRepair.Item(sKey),sLocalMsi\nEnd If &#039;fMsiRename\nIf oFso.FileExists(sLocalMsi) _\nThen sRepair = &quot;Restored: Successfully connected to &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msi package &quot; _\nElse fTrySource = True\n\n&#039;Handle &#039;DetectOnly&#039; exception\nIf fDetectOnly Then\nsRepair = &quot;Note: Restore is possible from &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msi package &quot;\nfTrySource = False\nEnd If &#039;fDetectOnly\nElse\nfTrySource = True\nEnd If\n\n&#039;Try to restore from resgistered sources\nIf fTrySource Then\n&#039;Obtain a productsex handle\nSet Prod = oMsi.ProductsEx(Product,&quot;&quot;,MSIINSTALLCONTEXT_ALL)(0)\n&#039;Get the sources\nSet MsiSources = Prod.Sources(1)\nsPackage = &quot;&quot;\nsPackage = Prod.SourceListInfo(&quot;PackageName&quot;)\nFor Each Source in MsiSources\nLog &quot;Debug: Trying to connect to resiliency source &quot;&amp;Source\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;&quot;Trying to connect to resiliency source &quot;&amp;Source\nIf fRepaired Then Exit For\nsFile = &quot;&quot;\nsFile = Source&amp;sPackage\nIf oFso.FileExists(sFile) Then\nsSourceKey = &quot;&quot;\nsSourceKey = GetMsiProductCode(sFile)&amp;&quot;_&quot;&amp;GetMsiPackageCode(sFile)\nIf sKey = sSourceKey Then\nIf Not dicRepair.Exists(sSourceKey) Then dicRepair.Add sSourceKey,sFile\nIf NOT fDetectOnly AND fMsiRename Then \nsFileName = &quot;&quot;\nSet File = oFso.GetFile(sLocalMsi)\nsFileName = File.Name\nFile.Name = &quot;Renamed_&quot;&amp;File.Name\nEnd If\nIf NOT fDetectOnly Then oFso.CopyFile sFile,sLocalMsi\nfRepaired = oFso.FileExists(sLocalMsi)\nIf fDetectOnly Then sRepair = &quot;Note: Restore is possible from &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msi package &quot;\nEnd If &#039;sKey = sSourceKey\nEnd If\nNext &#039;Source\nIf fRepaired Then sRepair = &quot;Restored: Successfully connected to &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msi package &quot;\nEnd If &#039;fTrySource\n\nIf NOT oFso.FileExists(sLocalMsi) AND fMsiRename Then\n&#039;Undo rename\nFile.Name = sFileName\nsRepair = &quot;Error: Attmpt failed to replace the mismatched file. Original cached file has been restored &quot;\nEnd If\nSet File = Nothing\n\n&#039;Log the result\nsTmp = vbTab&amp;sRepair&amp;sLocalMsi&amp;&quot; (PackageCode: &quot;&amp;sRegPackageCode&amp;&quot;)&quot;\nLog sTmp\nEnd If\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If\n\n&#039;Check local .msp packages\nFor iReLoop = 0 To 1\nfReLoop = False\nSet PatchList = oMsi.PatchesEx(Product,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,MSIPATCHSTATE_APPLIED + MSIPATCHSTATE_SUPERSEDED + MSIPATCHSTATE_OBSOLETED)\nIf Err = 0 Then\nFor Each Patch in PatchList\nErr.Clear\nsLocalMsp = &quot;&quot; : sRepair = &quot;&quot; : fTrySource = False : fRepaired = False\nsLocalMsp = LCase(Patch.PatchProperty(&quot;LocalPackage&quot;))\nIf Not dicMspChecked.Exists(Patch.PatchCode) Then dicMspChecked.Add Patch.PatchCode,sLocalMsp\nIf Not Err = 0 Then\nErr.Clear\n&#039;This happens if a patch is registered but the global patch registration has gone missing.\n&#039;To work around this a correction entry is created\nsTmp = vbTab&amp;&quot;Error: Failed to obtain local patch package data for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Fixing patch registration.&quot;\nIf fDetectOnly Then sTmp = vbTab&amp;&quot;Error: Failed to obtain local patch package data for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Patch registration would be fixed.&quot;\nLog sTmp\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT fDetectOnly Then\nFixMspGlobalReg Patch.PatchCode\nfReLoop = True\nsLocalMsp = LCase(Patch.PatchProperty(&quot;LocalPackage&quot;))\nEnd If\nEnd If\nIf NOT sLocalMsp = &quot;&quot; Then\nIf oFso.FileExists(sLocalMsp) Then \nLog vbTab&amp;&quot;Success: Confirmed local patch package as &#039;&quot;&amp;sLocalMsp&amp;&quot;&#039;&quot;&amp;vbTab&amp;&quot;for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot;&#039;.&quot;\nElse\n&#039;Try to restore from available resources\nsRepair = &quot;Error: Local .msp package missing. Attempt failed to restore &#039;&quot;\nsKey = &quot;&quot;\nsKey = Patch.PatchCode\nIf dicRepair.Exists(sKey) Then\nIf NOT fDetectOnly Then oFso.CopyFile dicRepair.Item(sKey),sLocalMsp\nIf oFso.FileExists(sLocalMsp) Then sRepair = &quot;Restored: Successfully connected to &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msp package &#039;&quot; Else fTrySource = True\n&#039;Handle &#039;DetectOnly&#039; exception\nIf fDetectOnly Then\nsRepair = &quot;Note: Restore is possible from &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msp package &quot;\nfTrySource = False\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\nEnd If &#039;fDetectOnly\nElse\nfTrySource = True\nEnd If\n\n&#039;Try to restore from resgistered sources\nIf fTrySource Then\n&#039;Get the sources\nsPackage = Patch.SourceListInfo(&quot;PackageName&quot;)\nSet MspSources = Patch.Sources(1)\nFor Each Source in MspSources\nIf fRepaired Then Exit For\nsFile = Source&amp;sPackage\nIf oFso.FileExists(sFile) Then\nsSourceKey = &quot;&quot;\nsSourceKey = oMsi.SummaryInformation(sFile,MSIOPENDATABASEMODE_READONLY).Property(PID_REVNUMBER)\nIf sKey = sSourceKey Then\nIf NOT dicRepair.Exists(sSourceKey) Then dicRepair.Add sSourceKey,sFile\nIf NOT fDetectOnly Then oFso.CopyFile sFile,sLocalMsp\nfRepaired = oFso.FileExists(sLocalMsp)\nIf fDetectOnly Then \nsRepair = &quot;Note: Restore is possible from &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msp package &quot;\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\nEnd If\nEnd If &#039;sKey = sSourceKey\nEnd If\nNext &#039;Source\nIf fRepaired Then\nsRepair = &quot;Restored: Successfully connected to &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msp package &quot;\nElse\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\nEnd If\nEnd If &#039;fTrySource\n\n&#039;Log the result\nsTmp = vbTab&amp;sRepair&amp;sLocalMsp&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nLog sTmp\nLogSummary Product,sTmp\nEnd If &#039;NOT oFso.FileExists\nEnd If &#039;Not sLocalMsp = &quot;&quot;\nIf NOT fReLoop Then EnsurePatchMetadata Patch,USERSID_NULL\nNext &#039;Patch\nElse\nsTmp = vbTab&amp;&quot;Error: PatchesEx API failed with error &quot; &amp; err.number &amp; &quot; - &quot; &amp; err.Description\nLog sTmp\nLogSummary Product,sTmp &amp;&quot; (Module RepairCache)&quot;\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If &#039;Err = 0\nIf NOT fReLoop Then Exit For\nNext &#039;iReLoop\n\nNext &#039;Prod\n\n&#039;In case that a global patch entry exists which is no longer linked to any product this is not covered\n&#039;in the logic above and requires this special handler\nsGlobalPatchesKey = REG_GLOBALCONFIG &amp; &quot;S-1-5-18\\Patches\\&quot;\nsClassesPatchesKey = &quot;Installer\\Patches\\&quot;\nIf RegEnumKey(HKLM,sGlobalPatchesKey,arrKeys) Then\nFor Each sKey in arrKeys\nPatch = GetExpandedGuid(sKey)\nIf NOT dicMspChecked.Exists(Patch) Then\n&#039;Only care if it&#039;s impacting known patches from the repair dictionary\nIf dicRepair.Exists(sKey) Then\n&#039;Flag to reconcile the registration to allow a clean transaction\nIf Not dicMspUnreg.Exists(sKey) Then dicMspUnreg.Add sKey,sKey\nEnd If &#039;dicRepair.Exists\nEnd If\nNext &#039;sKey\nEnd If &#039;RegEnumKey sGlobalPatchesKey\nIf RegEnumKey(HKCR,sClassesPatchesKey,arrKeys) Then\nFor Each sKey in arrKeys\nPatch = GetExpandedGuid(sKey)\nIf NOT dicMspChecked.Exists(Patch) Then\n&#039;Only care if it&#039;s impacting known patches from the repair dictionary\nIf dicRepair.Exists(sKey) Then\n&#039;Flag to reconcile the registration to allow a clean transaction\nIf Not dicMspUnreg.Exists(sKey) Then dicMspUnreg.Add sKey,sKey\nEnd If &#039;dicRepair.Exists\nEnd If\nNext &#039;sKey\nEnd If &#039;RegEnumKey sClassesPatchesKey\nIf dicMspUnreg.Count &gt; 0 Then\nFor Each sKey in dicMspUnreg.Keys\nRegDeleteKey HKLM,sGlobalPatchesKey &amp; sKey &amp; &quot;\\&quot;\nRegDeleteKey HKCR,sClassesPatchesKey &amp; sKey &amp; &quot;\\&quot;\nNext &#039;sKey\nEnd If &#039;dicMspUnreg &gt; 0\n\nErr.Clear\nSet PatchList = oMsi.PatchesEx(&quot;&quot;,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,MSIPATCHSTATE_APPLIED + MSIPATCHSTATE_SUPERSEDED + MSIPATCHSTATE_OBSOLETED)\nIf Err = 0 Then\nFor Each Patch in PatchList\n&#039;Only care if it&#039;s not a patch with known issues\nIf NOT dicMspChecked.Exists(Patch.PatchCode) Then\nErr.Clear\nsLocalMsp = &quot;&quot; : sRepair = &quot;&quot; : fTrySource = False : fRepaired = False\nsLocalMsp = LCase(Patch.PatchProperty(&quot;LocalPackage&quot;))\nIf Not Err = 0 Then\nErr.Clear\n&#039;This happens if a patch is registered but the global patch registration has gone missing.\n&#039;To work around this a correction entry is created\nsTmp = vbTab&amp;&quot;Error: Failed to obtain local patch package data for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Fixing patch registration.&quot;\nIf fDetectOnly Then sTmp = vbTab&amp;&quot;Error: Failed to obtain local patch package data for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Patch registration would be fixed.&quot;\nLog sTmp\nLogSummary &quot;&quot;,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT fDetectOnly Then\nFixMspGlobalReg Patch.PatchCode\nsLocalMsp = LCase(Patch.PatchProperty(&quot;LocalPackage&quot;))\nEnd If\nEnd If\nIf NOT sLocalMsp = &quot;&quot; Then\nIf oFso.FileExists(sLocalMsp) Then \nLog vbTab&amp;&quot;Success: Confirmed local patch package as &#039;&quot;&amp;sLocalMsp&amp;&quot;&#039;&quot;&amp;vbTab&amp;&quot;for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot;&#039;.&quot;\nElse\n&#039;Try to restore from available resources\nsRepair = &quot;Error: Local .msp package missing. Attempt failed to restore &#039;&quot;\nsKey = &quot;&quot;\nsKey = Patch.PatchCode\nIf dicRepair.Exists(sKey) Then\nIf NOT fDetectOnly Then oFso.CopyFile dicRepair.Item(sKey),sLocalMsp\nIf oFso.FileExists(sLocalMsp) Then sRepair = &quot;Restored: Successfully connected to &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msp package &#039;&quot; Else fTrySource = True\n&#039;Handle &#039;DetectOnly&#039; exception\nIf fDetectOnly Then\nsRepair = &quot;Note: Restore is possible from &#039;RestoreLocation&#039; (&quot;&amp;dicRepair.Item(sKey)&amp;&quot;) to restore local .msp package &quot;\nfTrySource = False\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\nEnd If &#039;fDetectOnly\nElse\nfTrySource = True\nEnd If\n\n&#039;Try to restore from resgistered sources\nIf fTrySource Then\n&#039;Get the sources\nsPackage = Patch.SourceListInfo(&quot;PackageName&quot;)\nSet MspSources = Patch.Sources(1)\nFor Each Source in MspSources\nIf fRepaired Then Exit For\nsFile = Source&amp;sPackage\nIf oFso.FileExists(sFile) Then\nsSourceKey = &quot;&quot;\nsSourceKey = oMsi.SummaryInformation(sFile,MSIOPENDATABASEMODE_READONLY).Property(PID_REVNUMBER)\nIf sKey = sSourceKey Then\nIf NOT dicRepair.Exists(sSourceKey) Then dicRepair.Add sSourceKey,sFile\nIf NOT fDetectOnly Then oFso.CopyFile sFile,sLocalMsp\nfRepaired = oFso.FileExists(sLocalMsp)\nIf fDetectOnly Then \nsRepair = &quot;Note: Restore is possible from &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msp package &quot;\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\nEnd If\nEnd If &#039;sKey = sSourceKey\nEnd If\nNext &#039;Source\nIf fRepaired Then\nsRepair = &quot;Restored: Successfully connected to &#039;registered InstallSource&#039; (&quot;&amp;sFile&amp;&quot;) to restore local .msp package &quot;\nElse\nIf NOT dicMspError.Exists(Patch.PatchCode) Then dicMspError.Add Patch.PatchCode,sLocalMsp\n&#039;MspReconcile logic is not designed to handle this special case. \n&#039;Unregister is called straight away if MspReconcile is scheduled to run\nIf NOT fDetectOnly AND fReconcileCache Then UnregisterPatch Patch\nEnd If\nEnd If &#039;fTrySource\n\n&#039;Log the result\nsTmp = vbTab&amp;sRepair&amp;sLocalMsp&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nLog sTmp\nLogSummary &quot;&quot;,sTmp\nEnd If &#039;NOT oFso.FileExists\nEnd If &#039;Not sLocalMsp = &quot;&quot;\nEnd If &#039;NOT dicMspError.Exists\nNext &#039;Patch\nElse\nsTmp = vbTab&amp;&quot;Error: PatchesEx API failed with error &quot; &amp; err.number &amp; &quot; - &quot; &amp; err.Description\nLog sTmp\nLogSummary &quot;&quot;,sTmp &amp;&quot; (Module RepairCache)&quot;\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If &#039;Err = 0\n\nEnd Sub &#039;RepairCache\n&#039;=======================================================================================================\n\n&#039;Unregister .msp files that have gone missing from the &#039;%windir%\\installer&#039; folder\nSub MspReconcile\n\nConst MAX_ATTEMPT = 100\n\nDim Prod,Product,Patch,PatchList,ProductsList\n\nDim sLocalMsp,sLocalMsi\n\nDim iCnt\n\nDim fResume,fMspOk\n\nDim dicMspUnregister\n\nOn Error Resume Next\n\nSet dicMspUnregister = CreateObject(&quot;Scripting.Dictionary&quot;)\n\nsTmp = &quot;Running Module - Msp Reconcile&quot;\nLog vbCrLf&amp;vbCrLf&amp;sTmp&amp;vbCrLf&amp; String(Len(sTmp),&quot;-&quot;)\nIf fCscript Then wscript.echo &quot;Scanning for broken patches&quot;\n\niCnt = 0\n\n&#039;Main detection loop\nSet ProductsList = oMsi.ProductsEx(&quot;&quot;,&quot;&quot;,MSIINSTALLCONTEXT_MACHINE)\nFor Each Prod in ProductsList\nProduct = Prod.ProductCode\nIf IsOfficeProduct (Product) Then\nLog vbCrLf&amp;&quot;Product: &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;)\nIf fCscript Then wscript.echo vbTab&amp;&quot;Scan &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;)\n\nIf NOT fRepairCache Then\n&#039;Check local .msi package\nsLocalMsi = &quot;&quot;\nsLocalMsi = oMsi.ProductInfo(Product,&quot;LocalPackage&quot;)\nIf oFso.FileExists(sLocalMsi) Then\nLog vbTab&amp;&quot;Success: Local .msi package &quot; &amp;sLocalMsi &amp; &quot; is available.&quot;\nElse\nIf sLocalMsi = &quot;&quot; Then \nsTmp = vbTab&amp;&quot;Error: No local .msi package registered.&quot;\nLog sTmp\nElse\nsTmp = vbTab&amp;&quot;Error: Local .msi package &quot; &amp;sLocalMsi &amp; &quot; is missing.&quot;\nLog sTmp\nEnd If\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If\nEnd If &#039;NOT fRepairCache\n\n&#039;Get the list of patches for the product\nfResume = True\nfMspOk = True\nDo While fResume \nErr.Clear\nfResume = False\nSet PatchList = oMsi.PatchesEx(Product,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,MSIPATCHSTATE_ALL)\nIf Err = 0 Then\nFor Each Patch in PatchList\nErr.Clear\nsLocalMsp = &quot;&quot; : sLocalMsp = LCase(Patch.PatchProperty(&quot;LocalPackage&quot;))\nIf Not Err = 0 Then\nfMspOk = False\nErr.Clear\nIf NOT dicMspUnregister.Exists(Patch.PatchCode) Then\nsTmp = vbTab&amp;&quot;Error: Failed to obtain local patch package data for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;&quot;\nLog sTmp\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If\nEnd If &#039;Err = 0\nIf NOT oFso.FileExists(sLocalMsp) Then \nsTmp = vbTab&amp;&quot;Error: Local patch package &#039;&quot;&amp;sLocalMsp&amp;&quot;&#039; missing for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot;&#039;. Unregistering patch \u2026&quot;\nIf fDetectOnly Then sTmp = vbTab&amp;&quot;Error: Local patch package &#039;&quot;&amp;sLocalMsp&amp;&quot;&#039; missing for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot;&#039;. This patch would need to be unregistered!&quot;\nLog sTmp\nLogSummary Product,sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\n&#039;Call patch unregister routine\nIf NOT dicMspUnregister.Exists(Patch.PatchCode) Then dicMspUnregister.Add Patch.PatchCode,Patch.PatchCode\niCnt = iCnt + 1\nIf iCnt &lt; MAX_ATTEMPT Then fResume = True\nIf NOT fDetectOnly Then UnregisterPatch Patch\n&#039;Refresh PatchesEx object and resume\nIf NOT fDetectOnly Then Exit For\n&#039;Reset flag for detect only case\nfResume = False\nfMspOk = False\nElse\nIf NOT fRepairCache Then Log vbTab&amp;&quot;Success: Confirmed local patch package as &#039;&quot;&amp;sLocalMsp&amp;&quot;&#039;&quot;&amp;vbTab&amp;&quot;for patch &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039; - &#039;&quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot;&#039;.&quot;\nEnd If\nNext &#039;Patch\nIf fMspOk Then Log vbTab&amp;&quot;Success: No locally cached .msp packages are missing.&quot;\nElse\nsTmp = vbTab&amp;&quot;Error: PatchesEx API failed with error &quot; &amp; err.number &amp; &quot; - &quot; &amp; err.Description\nLog sTmp\nLogSummary Product,sTmp &amp;&quot; (Module MspReconcile)&quot;\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If &#039;Err = 0\nLoop\nEnd If &#039;IsOfficeProduct\nNext &#039;Product\n\nEnd Sub &#039;MspReconcile\n&#039;=======================================================================================================\n\n&#039;Run patch detection to apply missing &amp; applicable patches\n&#039;Default is to search for patches in\n&#039;A) provided SUpdateLocation folders\n&#039;B) current directory from which script is called\n&#039;C) %windir%\\installer\\\nSub ApplyPatches\n\nDim Product,Patch,Key\n\nDim sPatches,sReturn\n\nDim iIndex\n\nOn Error Resume Next\n\nIf fApplyPatch Then sTmp = &quot;Running ApplyPatch&quot; Else sTmp = &quot;Running ApplyPatch for SUpdateLocation folder &quot;\nIf fViewPatch Then sTmp = &quot;Running applicable patch detection&quot;\nLog vbCrLf&amp;vbCrLf&amp;sTmp &amp;vbCrLf&amp; String(Len(sTmp),&quot;-&quot;)\nIf fCscript Then wscript.echo &quot;Running applicable patch detection&quot;\n\nsPatches = &quot;&quot;\n\n&#039;Init the dictionary objects\nSet dicMspNoSeq = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspMinor = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspSmall = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspObsoleted = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicMspSequence = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;Get the patch references from all locations\n&#039;This calls into the patch details routine as well\nCollectSUpdates\nLog &quot;Debug: Found &quot; &amp; UBound(arrSUpdatesAll)+1 &amp; &quot; unique patch(es) in total.&quot; &amp; vbCrLf\nIf fCscript Then wscript.echo vbTab&amp;&quot;Found &quot; &amp; UBound(arrSUpdatesAll)+1 &amp; &quot; unique patch(es) in total.&quot;\n\n&#039;Loop all products (filter on Office products) and call the pre-sequencer\nFor Each Product in oMsi.Products\nIf IsOfficeProduct(Product) Then\nLog vbCrLf&amp;&quot;Product: &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;) &amp; &quot;, Build: &quot; &amp; oMsi.ProductInfo(Product,&quot;VersionString&quot;)\nIf fCscript Then wscript.echo vbCrLf &amp; vbTab&amp;&quot;Scan &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;) &amp; &quot;, Build: &quot; &amp; oMsi.ProductInfo(Product,&quot;VersionString&quot;)\n\n&#039;Ensure empty dics\ndicMspNoSeq.RemoveAll\ndicMspMinor.RemoveAll\ndicMspSmall.RemoveAll\ndicMspObsoleted.RemoveAll\ndicMspSequence.RemoveAll\n\n&#039;Ensure empty value(s)\nsProductVersionReal = &quot;&quot;\n\n&#039;Fill the dictionary objects with a raw list of applicable patches\nGetRawBuckets Product\n\n&#039;Sequence the MinorUpdate bucket first to ensure we get the correct new build number\nSequenceMspMinor Product\n&#039;Sequence the 2.x NoSequence bucket\nSequenceMspNoSeq Product\n&#039;Sequence the SmallUpdate bucket\nSequenceMspSmall Product\nLog vbTab&amp;&quot;Debug: 2.x style patches bucket contains &quot;&amp;dicMspNoSeq.Count&amp;&quot; patch(es) after sequencing.&quot;\nLog vbTab&amp;&quot;Debug: Minor Update (service pack) bucket contains &quot;&amp;dicMspMinor.Count&amp;&quot; patch(es) after sequencing.&quot;\nLog vbTab&amp;&quot;Debug: Small Patches bucket contains &quot;&amp;dicMspSmall.Count&amp;&quot; patch(es) after sequencing.&quot;\n\nIf NOT fViewPatch Then\n&#039;Invoke msiexec to apply the list of identified patches\n\n&#039;Execute baseline bucket (minor update aka service pack)\nsPatches = &quot;&quot;\nFor Each Key in dicMspMinor.Keys\niIndex = dicMspMinor.Item(Key)\nsPatches = sPatches&amp;&quot;;&quot;&amp;arrSUpdatesAll(dicMspMinor.Item(Key),COL_FILENAME)\nNext &#039;Key\nIf Len(sPatches)&gt;0 Then sReturn = ApplyPatch(Product,sPatches)\n\n&#039;Execute the 2.x and small patches bucket\nsPatches = &quot;&quot;\nFor Each Key in dicMspNoSeq.Keys\niIndex = dicMspNoSeq.Item(Key)\nsPatches = sPatches&amp;&quot;;&quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nNext &#039;Key\nIf Len(sPatches)&gt;0 Then sReturn = ApplyPatch(Product,sPatches)\nFor Each Key in dicMspSmall.Keys\niIndex = dicMspSmall.Item(Key)\nsPatches = sPatches&amp;&quot;;&quot;&amp;arrSUpdatesAll(dicMspSmall.Item(Key),COL_FILENAME)\nNext &#039;Key\nIf Len(sPatches)&gt;0 Then sReturn = ApplyPatch(Product,sPatches)\nElse\n\nEnd If &#039;fViewPatch\n\nEnd If &#039;IsOfficeProduct\nNext &#039;Product\n\nEnd Sub &#039;ApplyPatches\n&#039;=======================================================================================================\n\n&#039;The arrUpdateLocations array is sorted and validated by now\n&#039;It cannot be empty since it does at least contain the current directory.\n&#039;As local folders are already sorted to the start of the array this will\n&#039;ensure that local .msp files are favored over network patches.\n&#039;Purpose if this routine is to have a reference and all metadata of\n&#039;available .msp files.\n&#039;This will be used as base to pre-sequence the applicable patches.\nSub CollectSUpdates\n\nDim File,Folder,SumInfo\nDim sKey,sPatchTargets,sFilter\nDim i,iCnt\n\nOn Error Resume Next\n\nSet dicSUpdatesAll = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;If NOT sApplyPatch = &quot;&quot; Then CheckPatchExtract\nCheckPatchExtract\n\niCnt = 0\n&#039;Collect a reference list of all patches\nFor Each Folder in arrUpdateLocations\nIf fCscript Then wscript.echo vbTab&amp;&quot;Collect files from &quot;&amp;Folder\nFor Each File in oFso.GetFolder(Folder).Files\nIf LCase(Right(File.Name,4)) = &quot;.msp&quot; Then\nIf LCase(File.Path)=LCase(sApplyPatch) OR sApplyPatch = &quot;&quot; OR (LCase(oFso.GetFolder(Folder).Path)&amp;&quot;\\&quot; = LCase(sWICacheDir)) Then\nSet SumInfo = Nothing\nSet SumInfo = oMsi.SummaryInformation(File.Path,MSIOPENDATABASEMODE_READONLY)\nsKey = &quot;&quot; : sPatchTargets = &quot;&quot;\nsKey = SumInfo.Property(PID_REVNUMBER)\nsPatchTargets = SumInfo.Property(PID_TEMPLATE)\nIf Not dicSUpdatesAll.Exists(sKey) Then\n&#039;Found new patch\nIf IsOfficePatch(sPatchTargets) Then\ndicSUpdatesAll.Add sKey,File.Path\nElse\nIf NOT LCase(oFso.GetFolder(Folder).Path)&amp;&quot;\\&quot; = LCase(sWICacheDir) Then\nsTmp = &quot;Not an Office patch. Excluding patch &quot;&amp;File.Path&amp;&quot; from detection sequence.&quot;\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nEnd If\nEnd If\nElse\nIf NOT Left(File.Path,Len(sWICacheDir)) = sWICacheDir Then\nsTmp = &quot;Excluding patch &quot;&amp;File.Path&amp;&quot; from detection sequence as duplicate of &quot; &amp; dicSUpdatesAll.Item(sKey)\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary &quot;Note:&quot;,vbTab&amp;sTmp\nEnd If\nEnd If &#039;dicSUpdatesAll.Exists\nEnd If\nEnd If &#039;.msp\nNext &#039;File\nLog &quot;Debug: Found &quot; &amp; dicSUpdatesAll.Count - iCnt &amp; &quot; unique patch(es) in folder &quot; &amp; Folder\nIf fCscript Then wscript.echo vbTab&amp;&quot;Found &quot; &amp; dicSUpdatesAll.Count - iCnt &amp; &quot; unique patch(es) in folder &quot; &amp; Folder\niCnt = dicSUpdatesAll.Count\nNext &#039;Folder\n\nIf dicSUpdatesAll.Count = 0 Then Exit Sub\n\n&#039;Initialize the patch details array\nReDim arrSUpdatesAll(dicSupdatesAll.Count-1,COL_MAX)\n\n&#039;Collect all patch details\ni = 0\nIf fCscript Then wscript.echo vbTab&amp;&quot;Obtaining patch details for identified patches&quot;\nFor Each key in dicSUpdatesAll.Keys\nAddPatchDetails dicSUpdatesAll.Item(key),i\ni=i+1\nNext &#039;key\n\nfUpdatesCollected = True\n\nEnd Sub &#039;CollectSUpdate\n&#039;=======================================================================================================\n\nSub AddPatchDetails(sMspPath,iIndex)\n\nDim SumInfo,Msp,Record\n\nDim sSiTmp,sChar,sTitle\n\nDim i,iSiCnt\n\nDim qView\n\nDim arrTitle,arrSi\n\nOn Error Resume Next\n\n&#039;Defaults\n&#039;\u2014\u2014-\nSet Record = Nothing\narrSUpdatesAll(iIndex,COL_APPLIEDCNT) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_NOQALBASELINECNT) = &quot;&quot;\n\n&#039;SummaryInformation\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\nSet SumInfo = oMsi.SummaryInformation(sMspPath,0)\narrSUpdatesAll(iIndex,COL_FILENAME) = sMspPath &#039;Msp FileName\narrSUpdatesAll(iIndex,COL_TARGETS) = SumInfo.Property(PID_TEMPLATE) &#039;PatchTargets\narrSUpdatesAll(iIndex,COL_PATCHCODE) = SumInfo.Property(PID_REVNUMBER) &#039;PatchCode\nIf Len(arrSUpdatesAll(iIndex,COL_PATCHCODE))&gt;LEN_GUID Then\narrSUpdatesAll(iIndex,COL_SUPERSEDES)=Mid(arrSUpdatesAll(iIndex,COL_PATCHCODE),LEN_GUID+1)\narrSUpdatesAll(iIndex,COL_PATCHCODE)=Left(arrSUpdatesAll(iIndex,COL_PATCHCODE),LEN_GUID)\nEnd If\n\n&#039;PatchXml\n&#039;\u2014\u2014-\narrSUpdatesAll(iIndex,COL_PATCHXML) = oMsi.ExtractPatchXMLData(arrSUpdatesAll(iIndex,COL_FILENAME))\n\n&#039;Other\n&#039;\u2014-\narrSUpdatesAll(iIndex,COL_REFCNT) = 0\n\n&#039;Patch tables\n&#039;\u2014\u2014\u2014\u2014\nSet Msp = oMsi.OpenDatabase(sMspPath,MSIOPENDATABASEMODE_PATCHFILE)\n\nIf Not Err = 0 Then\n&#039;An error at this points indicates a severe issue\nsTmp = &quot;Failed to read data from .msp package &quot;&amp;sMspPath\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary &quot;Error:&quot;,vbTab&amp;sTmp\nExit Sub\nEnd If\narrSUpdatesAll(iIndex,COL_PATCHTABLES) = GetDatabaseTables(Msp)\n\nIf InStr(arrSUpdatesAll(iIndex,COL_PATCHTABLES),&quot;MsiPatchMetadata&quot;)&gt;0 Then\n&#039;KB\nSet qView = Msp.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;KBArticle Number&#039;&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\narrSUpdatesAll(iIndex,COL_KB) = UCase(Record.StringData(2))\narrSUpdatesAll(iIndex,COL_KB) = Replace(arrSUpdatesAll(iIndex,COL_KB),&quot;KB&quot;,&quot;&quot;)\nElse\narrSUpdatesAll(iIndex,COL_KB) = &quot;&quot;\nEnd If\nqView.Close\n&#039;StdPackageName\nSet qView = Msp.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;StdPackageName&#039;&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\narrSUpdatesAll(iIndex,COL_PACKAGE) = Record.StringData(2)\nElse\narrSUpdatesAll(iIndex,COL_PACKAGE) = &quot;&quot;\nEnd If\nqView.Close\n&#039;Release (required for SP uninstall)\nSet qView = Msp.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;Release&#039;&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\narrSUpdatesAll(iIndex,COL_RELEASE) = Record.StringData(2)\nElse\narrSUpdatesAll(iIndex,COL_RELEASE) = &quot;&quot;\nEnd If\nqView.Close\nElse\narrSUpdatesAll(iIndex,COL_KB) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_PACKAGE) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_RELEASE) = &quot;&quot;\nEnd If\n\nIf arrSUpdatesAll(iIndex,COL_KB) = &quot;&quot; Then\n&#039;Scan the SummaryInformation data for the KB\nFor iSiCnt = 1 To 2\nSelect Case iSiCnt\nCase 1\narrSi = Split(SumInfo.Property(PID_SUBJECT),&quot;;&quot;)\nCase 2\narrSi = Split(SumInfo.Property(PID_TITLE),&quot;;&quot;)\nEnd Select\n\nIf IsArray(arrSi) Then\nFor Each sTitle in arrSi\nsSiTmp = &quot;&quot;\nsSiTmp = Replace(UCase(sTitle),&quot; &quot;,&quot;&quot;)\nIf InStr(sSiTmp,&quot;KB&quot;)&gt;0 Then\n&#039;Strip the KB\nsSiTmp = Mid(sSiTmp,InStr(sSiTmp,&quot;KB&quot;)+2)\nFor i = 1 To Len(sSiTmp)\nsChar = &quot;&quot;\nsChar = Mid(sSiTmp,i,1)\nIf (Asc(sChar) &gt;= 48 AND Asc(sChar) &lt;= 57) Then arrSUpdatesAll(iIndex,COL_KB)=arrSUpdatesAll(iIndex,COL_KB)&amp;sChar\nNext &#039;i\n&#039;Ensure a valid length\nIf Len(arrSUpdatesAll(iIndex,COL_KB))&lt;5 Then arrSUpdatesAll(iIndex,COL_KB)=&quot;&quot; Else Exit For\nEnd If\nNext\nIf Len(arrSUpdatesAll(iIndex,COL_KB))&gt;4 Then Exit For\nEnd If &#039;IsArray(arrSi)\nNext &#039;iSiCnt\nEnd If\n\n&#039;PatchSequence &amp; PatchFamily\nIf InStr(arrSUpdatesAll(iIndex,COL_PATCHTABLES),&quot;MsiPatchSequence&quot;)&gt;0 Then\nSet qView = Msp.OpenView(&quot;SELECT `PatchFamily`,`Sequence` FROM MsiPatchSequence&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\nDo Until Record Is Nothing\narrSUpdatesAll(iIndex,COL_FAMILY) = arrSUpdatesAll(iIndex,COL_FAMILY)&amp;&quot;;&quot;&amp;Record.StringData(1)\narrSUpdatesAll(iIndex,COL_SEQUENCE) = arrSUpdatesAll(iIndex,COL_SEQUENCE)&amp;&quot;;&quot;&amp;Record.StringData(2)\nSet Record = qView.Fetch()\nLoop\narrSUpdatesAll(iIndex,COL_FAMILY) = Mid(arrSUpdatesAll(iIndex,COL_FAMILY),2)\narrSUpdatesAll(iIndex,COL_SEQUENCE) = Mid(arrSUpdatesAll(iIndex,COL_SEQUENCE),2)\nElse\narrSUpdatesAll(iIndex,COL_FAMILY) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_SEQUENCE) = &quot;0&quot;\nEnd If\nqView.Close\nElse\narrSUpdatesAll(iIndex,COL_FAMILY) = &quot;&quot;\narrSUpdatesAll(iIndex,COL_SEQUENCE) = &quot;0&quot;\nEnd If\n\narrTitle = Split(SumInfo.Property(PID_TITLE),&quot;;&quot;)\nIf UBound(arrTitle)&gt;0 Then\nIf arrSUpdatesAll(iIndex,COL_FAMILY)=&quot;&quot; Then arrSUpdatesAll(iIndex,COL_FAMILY) = arrTitle(1)\nIf arrSUpdatesAll(iIndex,COL_PACKAGE)= &quot;&quot; Then arrSUpdatesAll(iIndex,COL_PACKAGE) = arrTitle(1)\nEnd If\n\n&#039;Exception handler for OCT patches\nIf arrSUpdatesAll(iIndex,COL_FAMILY) = &quot;SetupCustomizationFile&quot; Then\narrSUpdatesAll(iIndex,COL_KB) = &quot;n\/a (SetupCustomizationFile)&quot;\narrSUpdatesAll(iIndex,COL_PACKAGE) = &quot;OCT&quot;\nIf IsBaselineRequired(&quot;&quot;,arrSUpdatesAll(iIndex,COL_PATCHXML)) Then _\nLogSummary &quot;Important Note:&quot;,sMspPath&amp;&quot; is a customization patch based on the original release of the OCT. A more recent OCT version is available from http:\/\/www.microsoft.com\/downloads\/details.aspx?displaylang=en&amp;FamilyID=73d955c0-da87-4bc2-bbf6-260e700519a8&#8243;&quot;\nEnd If\n\nEnd Sub &#039;AddPatchDetails\n&#039;=======================================================================================================\n\n&#039;This function parses all available patches and \n&#039;fills the unsequenced buckets for the given Product(Code)\n&#039;Already Installed patches are excluded here\nSub GetRawBuckets(sProductCode)\n\nDim PatchList,Patch\n\nDim sAppliedPatches,sApplicablePatches,sProductVersion,sDetectedVersion\n\nDim iBucket,iIndex\n\nDim fSkipRealDetection, fIsWICached\n\nOn Error Resume Next\n\n&#039;Get installed patches\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\nSet PatchList = oMsi.PatchesEx(sProductCode,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,MSIPATCHSTATE_ALL)\nsAppliedPatches = &quot;&quot;\nFor Each Patch in PatchList\nsAppliedPatches = sAppliedPatches &amp; &quot;;&quot; &amp; Patch.PatchCode\nNext &#039;Patch\nfSkipRealDetection = False\nIf Not Err=0 Then fSkipRealDetection = True\n\n&#039;Sort the patches based on their bucket\nFor iIndex = 0 To UBound(arrSUpdatesAll)\n&#039;Honor the fExcludeCache and fIncludeOctCache flag\nfIsWICached = False\nIf Len(arrSUpdatesAll(iIndex,COL_FILENAME)) &gt; Len(sWICacheDir) Then\nfIsWICached = (LCase(Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir))) = LCase(sWICacheDir))\nEnd If\nIf ( (NOT fExcludeCache) OR (NOT fIsWICached) ) AND _\nNOT ( (arrSUpdatesAll(iIndex,COL_FAMILY)=&quot;SetupCustomizationFile&quot;) AND (fIsWICached) AND (NOT fIncludeOctCache) ) Then\n&#039;Exclude patches that do\n&#039; - not target the product\n&#039; - are already applied\nIf (InStr(arrSUpdatesAll(iIndex,COL_TARGETS),sProductCode)&gt;0) Then\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_REFCNT) = arrSUpdatesAll(iIndex,COL_REFCNT)+1\nIf (NOT InStr(sAppliedPatches,arrSUpdatesAll(iIndex,COL_PATCHCODE))&gt;0) Then\n&#039;Patch targets the current product and is not applied\nSelect Case GetMspBucket(sProductCode,iIndex)\nCase MSP_NOSEQ\ndicMspNoSeq.Add arrSUpdatesAll(iIndex,COL_PATCHCODE),iIndex\nCase MSP_MINOR\ndicMspMinor.Add arrSUpdatesAll(iIndex,COL_PATCHCODE),iIndex\nCase MSP_SMALL\ndicMspSmall.Add arrSUpdatesAll(iIndex,COL_PATCHCODE),iIndex\nCase Else\nEnd Select\nElse\nIf NOT fIsWICached Then\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_APPLIEDCNT) = arrSUpdatesAll(iIndex,COL_APPLIEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; is already installed for this product.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nEnd If\nEnd If\nElse\nIf NOT fIsWICached Then\nsTmp = &quot;Patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; (&quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp;&quot;) does not target this product.&quot;\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nEnd If\nEnd If\nElse\nIf fExcludeCache Then sTmp=&quot;ExcludeCache=True&quot; Else sTmp=&quot;IncludeOctCache=False&quot;\nIf (InStr(arrSUpdatesAll(iIndex,COL_TARGETS),sProductCode)&gt;0) Then _\nLog vbTab&amp;&quot;Debug: Excluding patch per &#039;&quot;&amp;sTmp&amp;&quot;&#039; filter. &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nEnd If &#039;fExcludeCache\nNext &#039;iIndex\n\n&#039;Validate integrity of the registered ProductVersion (build) as the sequencing\n&#039;logic relies on the correctness of this value.\nIf NOT fSkipRealDetection Then\nsProductVersion = oMsi.ProductInfo(sProductCode,&quot;VersionString&quot;)\nsDetectedVersion = GetRealBuildVersion(sAppliedPatches,sProductCode)\nIf NOT sProductVersion = sDetectedVersion Then\nsTmp = vbTab&amp;&quot;Error: Registered build version does not match detected build version. Registered build: &quot;&amp;sProductVersion&amp;&quot;. Detected build: &quot;&amp;sDetectedVersion\nIf fDetectOnly Then\nsTmp = sTmp &amp; &quot;. Registered build would be corrected to &quot;&amp;sDetectedVersion\nElse\nsTmp = sTmp &amp; &quot;. Updated registered build to new value &quot;&amp;sDetectedVersion\nUpdateProductVersion sProductCode,sDetectedVersion\nEnd If\nLog sTmp\nLogSummary sProductCode,sTmp\nEnd If\nEnd If\n\nEnd Sub &#039;GetRawBuckets\n&#039;=======================================================================================================\n\nFunction GetMspBucket(sProductCode,iIndex)\n\nOn Error Resume Next\n\n&#039;Defaults\nGetMspBucket = MSP_NOSEQ &#039;Default to 2.x NoSequence bucket\n\n&#039;Check if it&#039;s a 3.x type patch which has sequence information\nIf arrSUpdatesAll(iIndex,COL_SEQUENCE) = &quot;0&quot; Then\n&#039;This is a 2.x patch. Only continue if it&#039;s a service pack\nIf NOT IsMinorUpdate(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML)) Then Exit Function\nEnd If\n\n&#039;Check if it&#039;s a &quot;Minor Upgrade&quot; aka &quot;Service Pack&quot; vs. as &quot;Small Update&quot;\nIf IsMinorUpdate(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML)) Then GetMspBucket = MSP_MINOR Else GetMspBucket = MSP_SMALL\n\nEnd Function &#039;GetMspBucket\n&#039;=======================================================================================================\n\n&#039;Sequence the Minor Update (service pack) bucket\n&#039;The logic relies on the Office specific assumption that a service pack:\n&#039;- is cumulative\n&#039;- always uses &quot;Equals&quot; as baseline verification \nSub SequenceMspMinor(sProductCode)\n\nDim Key,Patch,Sequences,Seq\n\nDim sProductVersion,sProductVersionMax,sMspApplicable,sFamily,sSeq,sErr\n\nDim iCntBld,iCntMsp,iIndex\n\nDim fSeqFound,fHihgerBaselineExists\n\nDim arrMspUpdatedVersions,arrMspSuperseded,dicMspUpdatedVersion,arrErr\n\nOn Error Resume Next\n\nfSeqFound = False\nsMspApplicable = &quot;&quot;\nSet dicMspUpdatedVersion = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;Get the current product build\nsProductVersion = sProductVersionReal\nsProductVersionNew = sProductVersion\n\n&#039;Get the updated build versions. Sorted descending\n&#039;This call will already filter out superseded patches\narrMspUpdatedVersions = GetMspUpdatedVersion(sProductCode,dicMspUpdatedVersion)\n\n&#039;Check if there&#039;s an updated version available\nfHihgerBaselineExists = (UBound(arrMspUpdatedVersions)&gt; -1)\n\n&#039;Iterate the patches if we have a higher baseline than the current\nIf fHihgerBaselineExists Then\nsProductVersionMax =arrMspUpdatedVersions(0)\n&#039;Find applicable patch sequence\nFor iCntBld = 0 To UBound(arrMspUpdatedVersions)\nFor Each Key in dicMspMinor.Keys\niIndex = dicMspMinor.Item(Key)\nIf IsValidVersion(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML),sProductVersionNew,sErr) Then\n&#039;Found new baseline. Add patch as applicable\n&#039;Remember new baseline\nsProductVersionNew = dicMspUpdatedVersion.Item(iIndex)\nfHihgerBaselineExists = (sProductVersionMax&gt;sProductVersionNew)\narrErr = Split(sErr,&quot;;&quot;,2)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = arrSUpdatesAll(iIndex,COL_APPLICABLECNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Found applicable service pack patch to update build from &quot;&amp;arrErr(1)&amp;&quot; to build &quot;&amp;sProductVersionNew&amp;&quot; : KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp; _\n&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nsMspApplicable = sMspApplicable&amp;&quot;;&quot;&amp;Key\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nExit For\nEnd If\nNext &#039;Key\nIf NOT fHihgerBaselineExists Then Exit For\nNext &#039;iCntBld\nEnd If &#039;fHihgerBaselineExists\n\nFor Each Key in dicMspMinor.Keys\nIf NOT InStr(sMspApplicable,Key)&gt;0 Then\n&#039;patch excluded because higher baseline has been found\niIndex = dicMspMinor.Item(Key)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Excluding patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; because it&#039;s superseded by a scheduled service pack installation.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nIf dicMspMinor.Exists(Key) Then dicMspMinor.Remove Key\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nIf (NOT Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir)) = sWICacheDir) Then LogSummary sProductCode,vbTab&amp;sTmp\nEnd If\nNext &#039;Key\n\n&#039;Add patch family sequence data if available\nFor Each Key in dicMspMinor.Keys\niIndex = dicMspMinor.Item(Key)\nAddSequenceData arrSUpdatesAll(iIndex,COL_PATCHXML)\nNext &#039;Key\n\n&#039;Add obsoletion data\nFor Each Key in dicMspMinor.Keys\nSet arrMspSuperseded = Nothing\niIndex = dicMspMinor.Item(Key)\narrMspSuperseded = Split(arrSUpdatesAll(iIndex,COL_SUPERSEDES),&quot;;&quot;)\nFor Each Patch in arrMspSuperseded\nIf NOT dicMspObsoleted.Exists(Patch) Then dicMspObsoleted.Add Patch,Patch\nNext &#039;Patch\nNext &#039;Key\n\nEnd Sub &#039;SequenceMspMinor\n&#039;=======================================================================================================\n\n&#039;Sequence the bucket with 2.x NoSequence patches\n&#039;Superseded (obsoleted) patches are filtered out \nSub SequenceMspNoSeq(sProductCode)\n\nDim Key,Patch\n\nDim sErr\n\nDim iIndex\n\nDim arrMspSuperseded,arrErr\n\nOn Error Resume Next\n\n&#039;Build list of obsoleted patches\nFor Each Key in dicMspNoSeq.Keys\nSet arrMspSuperseded = Nothing\niIndex = dicMspNoSeq.Item(Key)\narrMspSuperseded = Split(arrSUpdatesAll(iIndex,COL_SUPERSEDES),&quot;;&quot;)\nFor Each Patch in arrMspSuperseded\nIf NOT dicMspObsoleted.Exists(Patch) Then dicMspObsoleted.Add Patch,Patch\nNext &#039;Patch\nNext &#039;Key\n\nFor Each Key in dicMspNoSeq.Keys\niIndex = dicMspNoSeq.Item(Key)\n&#039;Remove patch if obsolete\nIf dicMspObsoleted.Exists(Key) Then\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nIf NOT Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir)) = sWICacheDir Then\nsTmp = &quot;Patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; is obsoleted by an already installed patch.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;. &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nEnd If\nIf dicMspNoSeq.Exists(Key) Then dicMspNoSeq.Remove Key\nEnd If\n&#039;Check if patch is applicable\nIf IsValidVersion(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML),sProductVersionNew,sErr) Then\narrErr = Split(sErr,&quot;;&quot;,2)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = arrSUpdatesAll(iIndex,COL_APPLICABLECNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Found applicable 2.x style patch: KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Applicable baseline: &quot; &amp; arrErr(1)\nElse\narrErr = Split(sErr,&quot;;&quot;,2)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_NOQALBASELINECNT) = arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT)&amp;sProductCode&amp;&quot;;&quot;\n&#039;Cache valid baselines\narrSUpdatesAll(iIndex,COL_PATCHBASELINES) = arrErr(1)\nsTmp = &quot;No valid baseline available for this 2.x style patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch baseline(s): &quot; &amp; arrErr(1)&amp; &quot;. Installed baseline: &quot; &amp;sProductVersionNew\nIf dicMspNoSeq.Exists(Key) Then dicMspNoSeq.Remove Key\nEnd If &#039;IsValidVersion\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nNext &#039;Key\n\nEnd Sub &#039;SequenceMspNoSeq\n&#039;=======================================================================================================\n\nSub SequenceMspSmall(sProductCode)\n\nDim Key,Element,Elements\n\nDim sMspApplicable,sFamily,sSeq,sErr\n\nDim iIndex\n\nDim fApplicable\n\nDim arrErr\n\nOn Error Resume Next\n\nsErr = &quot;&quot;\n&#039;Determine current patch family sequence\nFor Each Key in dicMspSmall.Keys\nfApplicable = False\niIndex = dicMspSmall.Item(Key)\n&#039;Load baselines the patch can be applied to\n&#039;Exclude patches that do not target the current baseline\nIf IsValidVersion(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML),sProductVersionNew,sErr) Then\nAddSequenceData(arrSUpdatesAll(iIndex,COL_PATCHXML))\nElse\narrErr = Split(sErr,&quot;;&quot;,2)\nIf arrErr(0) = &quot;1&quot; Then\n&#039;Patch is superseded by the installed baseline (service pack)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; is superseded by an already installed service pack.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch baseline(s): &quot; &amp; arrErr(1)&amp; &quot;. Installed baseline: &quot; &amp;sProductVersionNew\nElse\n&#039;Patch excluded because it does not apply to the available baseline\n&#039;Cache valid baselines\narrSUpdatesAll(iIndex,COL_PATCHBASELINES) = arrErr(1)\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_NOQALBASELINECNT) = arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;No valid baseline available for patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch baseline(s): &quot; &amp; arrErr(1)&amp; &quot;. Installed baseline: &quot; &amp;sProductVersionNew\nEnd If\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nIf (NOT Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir)) = sWICacheDir) Then LogSummary sProductCode,vbTab&amp;sTmp\nIf dicMspSmall.Exists(Key) Then dicMspSmall.Remove Key\nEnd If\nNext &#039;Key\n\n&#039;Determine applicable patches\nFor Each Key in dicMspSmall.Keys\nfApplicable = False\niIndex = dicMspSmall.Item(Key)\nXmlDoc.LoadXml(arrSUpdatesAll(iIndex,COL_PATCHXML))\nSet Elements = XmlDoc.GetElementsByTagName(&quot;SequenceData&quot;)\nFor Each Element in Elements\nsFamily=&quot;&quot; : sSeq=&quot;&quot;\nsFamily = Element.selectSingleNode(&quot;PatchFamily&quot;).text\nsSeq = Element.selectSingleNode(&quot;Sequence&quot;).text\nsTmp = &quot;Found applicable patch with sequence version &quot;&amp;sSeq&amp;&quot; : KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)\nIf dicMspSequence.Exists(sFamily) Then\nIf sSeq = dicMspSequence.Item(sFamily) Then\nfApplicable = True\nEnd If\nElse\nfApplicable = True\nEnd If\nNext &#039;Element\n\nIf NOT fApplicable Then\n&#039;patch excluded because higher family patch available\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; is superseded by a later patch of the same family.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch build: &quot; &amp; sSeq&amp; &quot;. Installed build: &quot; &amp;dicMspSequence.Item(sFamily)\nIf dicMspSmall.Exists(Key) Then dicMspSmall.Remove Key\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nIf (NOT Left(arrSUpdatesAll(iIndex,COL_FILENAME),Len(sWICacheDir)) = sWICacheDir) Then LogSummary sProductCode,vbTab&amp;sTmp\nElse\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = arrSUpdatesAll(iIndex,COL_APPLICABLECNT)&amp;sProductCode&amp;&quot;;&quot;\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nEnd If\nNext &#039;Key\n\nEnd Sub &#039;SequenceMspSmall\n&#039;=======================================================================================================\n\n&#039;Return the updated version (build) of a minor update (service pack)\n&#039;sorted from highest to lowest\nFunction GetMspUpdatedVersion(sProductCode,dicMspUpdatedVersion)\n\nDim Key,Element,Elements\n\nDim sVersions,sProductVersion,sProductVersionMsi,sErr\n\nDim iIndex,iArrCnt\n\nDim arrVersions,arrErr,dicTmp\n\nOn Error Resume Next\n\nSet dicTmp = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;Get the current product build\nsProductVersion = sProductVersionReal\nsProductVersionMsi = &quot;&quot;\nsProductVersionMsi = GetMsiProductVersion(oMsi.ProductInfo(sProductCode,&quot;LocalPackage&quot;))\nIf sProductVersionMsi = &quot;&quot; Then sProductVersionMsi = sProductVersionReal\n\n&#039;Identify the available updated build (UpdatedVersion)\nsVersions = &quot;&quot; : sErr = &quot;&quot;\nFor Each Key in dicMspMinor.Keys\niIndex = dicMspMinor.Item(Key)\n&#039;Don&#039;t assume we have a valid RTM build. Beta products may break the logic!\nXmlDoc.LoadXml(arrSUpdatesAll(iIndex,COL_PATCHXML))\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nIf IsValidVersion(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML),sProductVersionMsi,sErr) Then\nIf Element.selectSingleNode(&quot;UpdatedVersion&quot;).text &gt; sProductVersion Then\nIf NOT dicTmp.Exists(iIndex) Then \ndicTmp.Add iIndex,Element.selectSingleNode(&quot;UpdatedVersion&quot;).text\nsVersions = sVersions&amp;&quot;;&quot;&amp;Element.selectSingleNode(&quot;UpdatedVersion&quot;).text\nEnd If\nElse\n&#039;patch excluded since not a higher baseline\n&#039;Update reference counter\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT)&amp;sProductCode&amp;&quot;;&quot;\nsTmp = &quot;Service pack patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot; is superseded by an already installed service pack.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nvbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch build: &quot;&amp;Element.selectSingleNode(&quot;UpdatedVersion&quot;).text&amp;&quot;, Installed build: &quot;&amp;sProductVersion\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nIf dicMspMinor.Exists(Key) Then dicMspMinor.Remove Key\nEnd If &#039;UpdatedVersion\nElse\n&#039;Not a RTM product\narrErr = Split(sErr,&quot;;&quot;,2)\nsTmp = &quot;No valid baseline available for service pack patch KB &quot;&amp;arrSUpdatesAll(iIndex,COL_KB)&amp;&quot;. This may indicate a BETA ProductVersion.&quot;&amp; _\n&quot; Patch details: &quot;&amp;arrSUpdatesAll(iIndex,COL_PATCHCODE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_PACKAGE)&amp;&quot;, &quot;&amp;arrSUpdatesAll(iIndex,COL_FILENAME)&amp; _\nbCrLf&amp;vbTab&amp;vbTab&amp;&quot;Patch baseline(s): &quot; &amp; arrErr(1)&amp; &quot;. Installed baseline: &quot; &amp;sProductVersion\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nEnd If &#039;IsValidVersion\nEnd If &#039;TargetProductCode\nNext &#039;Element\nNext &#039;Key\n\nIf sVersions = &quot;&quot; Then\nRedim arrVersions(-1)\nGetMspUpdatedVersion = arrVersions\nExit Function\nEnd If\n\n&#039;Sort descending\narrVersions = BubbleSort(Split(Mid(sVersions,2),&quot;;&quot;))\n\n&#039;Build the dictionary\nFor iArrCnt = 0 To UBound(arrVersions)\nFor Each Key in dicTmp.Keys\nIf dicTmp.Item(Key)=arrVersions(iArrCnt) AND NOT dicMspUpdatedVersion.Exists(Key) Then dicMspUpdatedVersion.Add Key,dicTmp.Item(Key)\nNext &#039;Key\nNext &#039;iArrCnt\n\n&#039;Return the sorted versions\nGetMspUpdatedVersion = arrVersions\n\nEnd Function &#039;GetMspUpdatedVersion\n&#039;=======================================================================================================\n\n&#039;Determine if a patch is applicable to the provided baseline\nFunction IsValidVersion(sProductCode,sXml,sProductVersion,sErr)\n\nDim Element,Elements,Node\n\nDim sCompType,sCompFlt,sVersion,sDelimiter,sTargets\n\nDim iCnt,iLoop,iRet\n\nDim fSuccess,fValidate\n\nDim arrLeftNum,arrRightNum\n\nOn Error Resume Next\n\nsErr = &quot;&quot; : sTargets = &quot;&quot; \nfValidate = True\nsDelimiter = Delimiter(sProductVersion)\narrLeftNum = Split(sProductVersion,sDelimiter)\n\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nfSuccess = False\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\n&#039;Collect the compare details\nsCompType = &quot;&quot; : sCompFlt = &quot;&quot; : sVersion = &quot;&quot;\nSet Node = Element.selectSingleNode(&quot;TargetVersion&quot;)\nsCompType = Node.getAttribute(&quot;ComparisonType&quot;)\nsCompFlt = Node.getAttribute(&quot;ComparisonFilter&quot;)\nsVersion = Node.text\nsTargets = sTargets&amp;&quot;;&quot;&amp;sVersion\nfValidate = CBool(Node.getAttribute(&quot;Validate&quot;))\nSet arrRightNum = Nothing\narrRightNum = Split(sVersion,sDelimiter)\n\n&#039;Set the filter setting\nSelect Case sCompFlt\nCase &quot;None&quot;\niLoop = -1\nCase &quot;Major&quot;\niLoop = 0\nCase &quot;MajorMinor&quot;\niLoop = 1\nCase &quot;MajorMinorUpdate&quot;\niLoop = 2\nCase Else\nEnd Select\n\n&#039;Compare the version strings based on the filter\niRet = -2\nFor iCnt = 0 To iLoop\niRet = StrComp(arrLeftNum(iCnt),arrRightNum(iCnt))\nIf NOT iRet = 0 Then Exit For\nNext &#039;iCnt\n\n&#039;Evaluate the compare result\nSelect Case sCompType\nCase &quot;LessThan&quot;\nfSuccess = (iRet = -1)\nCase &quot;LessThanOrEqual&quot;\nfSuccess = ((iRet = -1) OR (iRet = 0))\nCase &quot;Equal&quot;\nfSuccess = (iRet = 0)\nCase &quot;GreaterThanOrEqual&quot;\nfSuccess = ((iRet = 1) OR (iRet = 0))\nCase &quot;GreaterThan&quot;\nfSuccess = (iRet = 1)\nCase &quot;None&quot;\nfSuccess = True\nCase Else\nEnd Select\n\nIf NOT fValidate Then fSuccess = True\nIf fSuccess Then Exit For\nEnd If\nNext\n\nIf fSuccess Then sErr = iRet&amp;&quot;;&quot;&amp;sVersion Else sErr = iRet&amp;&quot;;&quot;&amp;Join(RemoveDuplicates(Split(Mid(sTargets,2),&quot;;&quot;)),&quot;;&quot;)\nIsValidVersion = fSuccess\n\nEnd Function &#039;IsValidVersion\n&#039;=======================================================================================================\n\n&#039;Return the tables of a given .msp file\nFunction GetDatabaseTables(MsiDb)\n\nDim ViewTables,Table\n\nDim sTables\n\nOn Error Resume Next\nsTables = &quot;&quot;\nSet Table = Nothing\nSet ViewTables = MsiDb.OpenView(&quot;SELECT `Name` FROM `_Tables` ORDER BY `Name`&quot;)\nViewTables.Execute\nDo\nSet Table = ViewTables.Fetch\nIf Table Is Nothing then Exit Do\nsTables = sTables&amp;&quot;,&quot;&amp;Table.StringData(1)\nLoop\nViewTables.Close\nIf Len(sTables)&gt;2 Then GetDatabaseTables=Mid(sTables,2)\n\nEnd Function &#039;GetDatabaseTables\n&#039;=======================================================================================================\n\n&#039;Returns the possible build versions the patch will be compared against\nFunction MspBldTargets(sProductCode,sXml)\n\nDim Element,Elements,Node\n\nDim sBaselines\n\nOn Error Resume Next\n\nsBaselines = &quot;&quot;\n&#039;Check baselines from XML\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nSet Node = Element.selectSingleNode(&quot;TargetVersion&quot;)\nsBaselines = sBaselines &amp; &quot;;&quot;&amp;Node.text\nEnd If\nNext\n\nIf Len(sBaselines)&gt;1 Then sBaselines = Mid(sBaselines,2)\nMspBldTargets = sBaselines\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Returns all possible build versions the patch will be compared against\nFunction GetMspBldTargets(sXml)\n\nDim Element,Elements,Node\n\nDim sBaselines\n\nOn Error Resume Next\n\nsBaselines = &quot;&quot;\n&#039;Check baselines from XML\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nSet Node = Element.selectSingleNode(&quot;TargetVersion&quot;)\nIf NOT InStr(sBaselines,Node.text) &gt; 0 then \nsBaselines = sBaselines &amp; &quot;;&quot;&amp;Node.text&amp;GetSpLevel(Node.text)\nEnd If\nNext\n\nIf Len(sBaselines)&gt;1 Then sBaselines = Mid(sBaselines,2)\nIf NOT IsBaselineRequired(&quot;&quot;,sXml) Then sBaselines = &quot;Baselineless Patch&quot;\nGetMspBldTargets = Replace(sBaselines,&quot;;&quot;,vbCrLf)\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Returns the comparison term for the build version validation\nFunction GetComparisonType(sProductCode,sXml)\n\nDim Element,Elements,Node\n\nOn Error Resume Next\n\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nSet Node = Element.selectSingleNode(&quot;TargetVersion&quot;)\nGetComparisonType = Node.getAttribute(&quot;ComparisonType&quot;)\nExit For\nEnd If\nNext\n\nEnd Function &#039;GetComparisonType\n&#039;=======================================================================================================\n\n&#039;Determines from patch xml if patch requires baseline validation\nFunction IsBaselineRequired(sProductCode,sXml)\n\nDim Element,Elements,Node\n\nOn Error Resume Next\n\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf sProductCode = &quot;&quot; Then sProductCode = Element.selectSingleNode(&quot;TargetProductCode&quot;).text\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nSet Node = Element.selectSingleNode(&quot;TargetVersion&quot;)\nIsBaselineRequired = CBool(Node.getAttribute(&quot;Validate&quot;))\nExit For\nEnd If\nNext\n\nEnd Function &#039;IsBaselineRequired\n&#039;=======================================================================================================\n\n&#039;Determine if the patch is a minor update (service pack)\nFunction IsMinorUpdate(sProductCode,sXml)\n\nDim Element,Elements,Node,ChildNodes\n\nOn Error Resume Next\n\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf sProductCode = &quot;&quot; Then sProductCode = Element.selectSingleNode(&quot;TargetProductCode&quot;).text\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nFor Each Node in Element.ChildNodes\nIf Node.NodeName = &quot;UpdatedVersion&quot; Then\nIsMinorUpdate = True\nExit Function\nEnd If\nNext &#039;Node\nEnd If\nNext &#039;Element\n\nEnd Function &#039;IsMinorUpdate\n&#039;=======================================================================================================\n\n&#039;Add patch family sequence information to the dictionary\nSub AddSequenceData(sXml)\n\nDim Element,Elements\n\nDim sFamily,sSeq\n\nOn Error Resume Next\n\nXmlDoc.LoadXml(sXml)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;SequenceData&quot;)\nFor Each Element in Elements\nsFamily=&quot;&quot; : sSeq=&quot;&quot;\nsFamily = Element.selectSingleNode(&quot;PatchFamily&quot;).text\nsSeq = Element.selectSingleNode(&quot;Sequence&quot;).text\n&#039;Only add to the family sequence number if it&#039;s marked to supersede earlier\nIf Element.selectSingleNode(&quot;Attributes&quot;).text = &quot;1&quot; Then\nIf dicMspSequence.Exists(sFamily) Then\nIf sSeq &gt; dicMspSequence.Item(Key) Then dicMspSequence.Item(sFamily)=sSeq\nElse\ndicMspSequence.Add sFamily,sSeq\nEnd If\nEnd If &#039;Attributes = 1\nNext &#039;Element\n\nEnd Sub &#039;AddSequenceData\n&#039;=======================================================================================================\n\n&#039;Detect and remove unreferenced .msp files from\n&#039;%windir%\\installer folder\nSub WICleanOrphans\n\nDim File,Patch,AllPatches,Product,AllProducts,oRefFilesDic\n\nDim sLocalFile,sTargetFolder,sFileErr\n\nDim iFoo\n\nDim fFoundOrphan,fMspOK,fMsiOK\n\nOn Error Resume Next\n\nsTmp = &quot;Running CleanCache to remove unreferenced .msi, .msp files from &quot; &amp; sWICacheDir\nLog vbCrLf&amp;vbCrLf&amp;sTmp &amp;vbCrLf&amp; String(Len(sTmp),&quot;-&quot;)\nIf fCscript Then wscript.echo &quot;Checking for unreferenced files in folder &quot;&amp; sWICacheDir\n\nfFoundOrphan = False\nfMspOK = True\nfMsiOK = True\nsTargetFolder = sTemp &amp; &quot;MovedCacheFiles\\&quot;\n\nErr.Clear\nSet oRefFilesDic = CreateObject(&quot;Scripting.Dictionary&quot;)\nIf Not Err = 0 Then Exit Sub\n\n&#039;Collect referenced .msp files\nIf fCscript Then wscript.echo vbTab&amp;&quot;Scanning .msp files&quot;\nFor iFoo = 1 To 1\nSet AllPatches = oMsi.PatchesEx(&quot;&quot;,USERSID_EVERYONE,MSIINSTALLCONTEXT_ALL,MSIPATCHSTATE_ALL)\nIf Not Err = 0 Then\nsTmp = &quot;Error: Failed to get a complete list of all .msp files. Aborting unreferenced .msp detection.&quot;\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nLog vbTab&amp;&quot; Source: &quot; &amp; Err.Source &amp; &quot;; Err# (Hex): &quot; &amp; Hex( Err ) &amp; _\n&quot;; Err# (Dec): &quot; &amp; Err &amp; &quot;; Description : &quot; &amp; Err.Description\nIf fCscript Then wscript.echo vbTab&amp;sTmp\nErr.Clear\nfMspOK = False\nExit For\nEnd If\nFor Each Patch in AllPatches\nsLocalFile = &quot;&quot;\nsLocalFile = LCase(Patch.Patchproperty(&quot;LocalPackage&quot;))\nIf NOT oRefFilesDic.Exists(sLocalFile) Then oRefFilesDic.Add sLocalFile,sLocalFile\nNext &#039;Patch\nIf Not Err = 0 Then\nsTmp = &quot;Error: Unhandled error. Aborting unreferenced .msp detection.&quot;\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nLog vbTab&amp;&quot; Source: &quot; &amp; Err.Source &amp; &quot;; Err# (Hex): &quot; &amp; Hex( Err ) &amp; _\n&quot;; Err# (Dec): &quot; &amp; Err &amp; &quot;; Description : &quot; &amp; Err.Description\nIf fCscript Then wscript.echo vbTab&amp;sTmp\nErr.Clear\nfMspOK = False\nExit For\nEnd If\nNext &#039;iFoo\n\n&#039;Collect referenced .msi files\nIf fCscript Then wscript.echo vbTab&amp;&quot;Scanning .msi files&quot;\nFor iFoo = 1 To 1\nSet AllProducts = oMsi.ProductsEx(&quot;&quot;,USERSID_EVERYONE,MSIINSTALLCONTEXT_ALL)\nIf Not Err = 0 Then\nsTmp = &quot;Error: Failed to get a complete list of all .msi files. Aborting unreferenced .msi detection.&quot;\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nLog vbTab&amp;&quot; Source: &quot; &amp; Err.Source &amp; &quot;; Err# (Hex): &quot; &amp; Hex( Err ) &amp; _\n&quot;; Err# (Dec): &quot; &amp; Err &amp; &quot;; Description : &quot; &amp; Err.Description\nIf fCscript Then wscript.echo vbTab&amp;sTmp\nErr.Clear\nfMsiOK = False\nExit For\nEnd If\nFor Each Product in AllProducts\nsLocalFile = &quot;&quot;\nsLocalFile = LCase(Product.InstallProperty(&quot;LocalPackage&quot;))\nIf NOT oRefFilesDic.Exists(sLocalFile) Then oRefFilesDic.Add sLocalFile,sLocalFile\nNext &#039;Patch\nIf Not Err = 0 Then\nsTmp = &quot;Error: Unhandled error. Aborting unreferenced .msi detection.&quot;\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nLog vbTab&amp;&quot; Source: &quot; &amp; Err.Source &amp; &quot;; Err# (Hex): &quot; &amp; Hex( Err ) &amp; _\n&quot;; Err# (Dec): &quot; &amp; Err &amp; &quot;; Description : &quot; &amp; Err.Description\nIf fCscript Then wscript.echo vbTab&amp;sTmp\nErr.Clear\nfMsiOK = False\nExit For\nEnd If\nNext &#039;iFoo\n\n&#039;Move unreferenced files\nFor Each File in oFso.GetFolder(sWICacheDir).Files\nIf Not Err = 0 Then\nLog vbTab&amp;&quot; Source: &quot; &amp; Err.Source &amp; &quot;; Err# (Hex): &quot; &amp; Hex( Err ) &amp; _\n&quot;; Err# (Dec): &quot; &amp; Err &amp; &quot;; Description : &quot; &amp; Err.Description\nSelect Case Err\nCase 70\n&#039;Permission denied. Skip this file\nsTmp = &quot;Note: File move operation failed. Skipping file &quot;&amp;sFileErr\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nErr.Clear\nCase Else\nsTmp = &quot;Error: Unhandled error. Aborting unreferenced file detection.&quot;\nLog vbTab&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nErr.Clear\nExit Sub\nEnd Select &#039;Err\nEnd If\nsFileErr = File.Name\nSelect Case LCase(Right(File.Name,4))\nCase &quot;.msp&quot;\nIf fMspOK Then\nIf NOT oRefFilesDic.Exists(LCase(File.Path)) Then\nfFoundOrphan = True\nIf Not oFso.FolderExists(sTargetFolder) Then oFso.CreateFolder sTargetFolder\nsTmp = &quot;Moving unreferenced file &quot; &amp; File.Path &amp;vbTab&amp; &quot; -&gt; &quot; &amp;sTargetFolder\nIf fDetectOnly Then sTmp = &quot;Identified unreferenced file &#039;&quot; &amp; File.Path &amp;&quot;&#039;. This would be moved to folder &quot; &amp;sTargetFolder\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT fDetectOnly Then\nIf oFso.FileExists(sTargetFolder&amp;File.Name) _ \nThen oFso.MoveFile File.Path,sTargetFolder&amp;sTimeStamp&amp;&quot;_&quot;&amp;File.Name _\nElse oFso.MoveFile File.Path,sTargetFolder&amp;File.Name\nEnd If &#039;fDetectOnly \nEnd If &#039;NOT oRefFilesDic.Exists\nEnd If &#039;fMspOK\nCase &quot;.msi&quot;\nIf fMsiOK Then\nIf NOT oRefFilesDic.Exists(LCase(File.Path)) Then\nfFoundOrphan = True\nIf Not oFso.FolderExists(sTargetFolder) Then oFso.CreateFolder sTargetFolder\nsTmp = &quot;Moving unreferenced file &quot; &amp; File.Path &amp;vbTab&amp; &quot; -&gt; &quot; &amp;sTargetFolder\nIf fDetectOnly Then sTmp = &quot;Identified unreferenced file &#039;&quot; &amp; File.Path &amp;&quot;&#039;. This would be moved to folder &quot; &amp;sTargetFolder \nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT fDetectOnly Then\nIf oFso.FileExists(sTargetFolder&amp;File.Name) _ \nThen oFso.MoveFile File.Path,sTargetFolder&amp;sTimeStamp&amp;&quot;_&quot;&amp;File.Name _\nElse oFso.MoveFile File.Path,sTargetFolder&amp;File.Name\nEnd If &#039;fDetectOnly \nEnd If &#039;NOT oRefFilesDic.Exists\nEnd If &#039;fMsiOK\nCase Else\nEnd Select\nNext &#039;File\n\nIf NOT fFoundOrphan Then Log vbTab&amp;&quot;Success: No unreferenced .msp files found.&quot;\n\n&#039;Delete the moved files in aggresive mode\nsTargetFolder = sTemp &amp; &quot;MovedCacheFiles&quot;\nIf fCleanAggressive Then\nsTmp = &quot;Deleting moved files folder &quot;&amp;sTargetFolder\nIf fDetectOnly Then sTmp = &quot;Moved files folder would be deleted: &quot;&amp;sTargetFolder\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary &quot;CleanCache&quot;,vbTab&amp;sTmp\nIf NOT fDetectOnly Then oFso.DeleteFolder sTargetFolder,True\nEnd If &#039;fCleanAggressive\n\nEnd Sub &#039;WICleanOrphans\n&#039;=======================================================================================================\n\n&#039;=======================================================================================================\n\n&#039;Uninstall a removable patch\nSub MspRemove(sPatchCodes,sProductCodes)\n\nDim Product,Patch,oPatches,Update\nDim sPatches,sCmd,sReturn,sStateFilter,sLogFilter\nDim iFoo,iActivityCnt\nDim arrLogFilter\nDim fPatchLoop,fSupersededMode,fMatchFound\n\nDim sPatchCodeCompressed,sProductCodeCompressed,sUserSid,sGlobalConfigKey,sMspFile\nDim fForceReconcile\nDim MspDb,Record\nDim qView\n\nOn Error Resume Next\n\niActivityCnt = 0\nIf UCase(sPatchCodes) = &quot;SUPERSEDED&quot; Then\nsStateFilter = MSIPATCHSTATE_SUPERSEDED\nfSupersededMode = True\nsLogFilter = &quot;superseded patches&quot;\nElse\nsStateFilter = MSIPATCHSTATE_ALL\nfSupersededMode = False\narrLogFilter = Split(sPatchCodes,&quot;;&quot;)\nsLogFilter = &quot;&quot;\nFor Each Patch in arrLogFilter\nIf InStr(Patch,&quot;{&quot;)&gt;0 Then sLogFilter = sLogFilter&amp;&quot;;&quot;&amp;Patch\nNext &#039;Patch\nSet Patch = Nothing\nIf NOT sLogFilter = &quot;&quot; Then sLogFilter=&quot;patch(es) &quot;&amp;Mid(sLogFilter,2)\nEnd If\n\n&#039;Loop all products\nFor Each Product In oMsi.Products\nIf IsOfficeProduct (Product) Then\nIf InStr(sProductCodes,Product)&gt;0 OR sProductCodes = &quot;&quot; Then\nFor iFoo = 1 To 1\nDo \nfPatchLoop = False\nfMatchFound = False\nLog &quot;Scanning &quot;&amp;Product&amp;&quot; - &quot;&amp;oMsi.ProductInfo(Product,&quot;ProductName&quot;)&amp;&quot; - for &quot;&amp;sLogFilter\nSet oPatches = oMsi.PatchesEx(Product,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,sStateFilter)\nIf Not Err = 0 Then\nErr.Clear\nLog vbCrLf&amp;&quot; Failed to retrieve list of patches&quot;\nIf fCscript Then wscript.echo vbTab&amp;&quot; Failed to retrieve list of patches&quot;\nExit For &#039;iFoo\nEnd If\nFor Each Patch in oPatches\nIf InStr(sPatchCodes,Patch.PatchCode)&gt;0 OR fSupersededMode Then\nfForceReconcile = False\nsMspFile = Patch.PatchProperty(&quot;LocalPackage&quot;)\niActivityCnt = iActivityCnt + 1\nsCmd = &quot;msiexec.exe \/i &quot; &amp; Product &amp; _\n&quot; MSIPATCHREMOVE=&quot;&amp;Patch.PatchCode&amp; _\n&quot; REBOOT=ReallySuppress&quot; &amp; _\n&quot; \/qb-&quot; &amp; _\n&quot; \/l*v+ %temp%\\&quot;&amp;Product&amp;&quot;_&quot;&amp;Patch.PatchCode&amp;&quot;_MspRemove.log&quot;\nIf Patch.PatchProperty(&quot;Uninstallable&quot;) = &quot;1&quot; Then\nfMatchFound = True\nsTmp = &quot;Uninstalling patch &quot; &amp; Patch.PatchCode&amp;&quot; - &quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nIf fDetectOnly Then\nsTmp = &quot;Uninstall attempt possible to remove patch &quot; &amp; Patch.PatchCode&amp;&quot; - &quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nEnd If &#039;fDetectOnly\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary Product,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT fDetectOnly Then \nLog vbTab&amp;&quot;Debug: calling msiexec with &#039;&quot;&amp;sCmd&amp;&quot;&#039;&quot;\n&#039;Execute the patch uninstall\nsReturn = CStr(oWShell.Run(sCmd, 0, True))\nfRebootRequired = fRebootRequired OR (sReturn = &quot;3010&quot;)\nsTmp = &quot;Msiexec patch removal returned: &quot; &amp; sReturn &amp;&quot; &quot;&amp; MsiexecRetval(sReturn)\nLog vbTab&amp;&quot;Debug: &quot;&amp; sTmp\nLogSummary Product,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT (sReturn=&quot;0&quot; OR sReturn=&quot;3010&quot;) AND oFso.FileExists(sMspFile) AND fForceRemovePatch Then fForceReconcile = True\nEnd If &#039;NOT fDetectOnly\nsPatches = sPatches &amp; Patch.PatchCode &amp; &quot;;&quot;\nElse\nIf fForceRemovePatch AND NOT fDetectOnly Then\nfMatchFound = True\nsTmp = &quot;Attmpting forced uninstall of patch &quot; &amp; Patch.PatchCode&amp;&quot; - &quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary Product,vbTab&amp;sTmp\n&#039;A) Tweak registry flag\n&#039;Fill variables\nsPatchCodeCompressed = GetCompressedGuid(Patch.PatchCode)\nsProductCodeCompressed = GetCompressedGuid(Patch.ProductCode)\nsUserSid = Patch.UserSid : If sUserSid = &quot;&quot; Then sUserSid = &quot;S-1-5-18\\&quot; Else sUserSid = sUserSid &amp; &quot;\\&quot;\nsGlobalConfigKey = REG_GLOBALCONFIG &amp; sUserSid &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot; \noFso.GetFile(sMspFile).Attributes = 0\nIf RegValExists(HKLM,sGlobalConfigKey&amp;sPatchCodeCompressed,&quot;Uninstallable&quot;) Then\noReg.SetDWordValue HKLM,sGlobalConfigKey&amp;sPatchCodeCompressed,&quot;Uninstallable&quot;,1\n&#039;B) Tweak cached .msp\nTweakDatabase(sMspFile)\n&#039;Call msiexec to uninstall patch\nLog vbTab&amp;&quot;Debug: calling msiexec with &#039;&quot;&amp;sCmd&amp;&quot;&#039;&quot;\n&#039;Execute the patch uninstall\nsReturn = CStr(oWShell.Run(sCmd, 0, True))\nfRebootRequired = fRebootRequired OR (sReturn = &quot;3010&quot;)\nsTmp = &quot;Msiexec patch removal returned: &quot; &amp; sReturn &amp;&quot; &quot;&amp; MsiexecRetval(sReturn)\nLog vbTab&amp;&quot;Debug: &quot;&amp; sTmp\nLogSummary Product,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nIf NOT (sReturn=&quot;0&quot; OR sReturn=&quot;3010&quot;) Then fForceReconcile = True\nElse\nfForceReconcile = True\nEnd If\nElse\nsTmp = &quot;Patch &quot; &amp; Patch.PatchCode&amp;&quot; - &quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)&amp;&quot; - is not uninstallable&quot;\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary Product,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nEnd If &#039;fForceRemovePatch\nEnd If &#039;Patch Uninstallable\n\nIf fForceReconcile Then \nsTmp = &quot;Msiexec based uninstall not possible. Unregistering patch &quot;\nsTmp = sTmp &amp; Patch.PatchCode&amp;&quot; - &quot;&amp;Patch.PatchProperty(&quot;DisplayName&quot;)\nLog vbTab&amp;&quot;Note: &quot;&amp;sTmp\nLogSummary Product,vbTab&amp;sTmp\nIf oFso.FileExists(sMspFile) Then oFso.MoveFile sMspFile,sTemp&amp;oFso.GetFileName(sMspFile)\nUnregisterPatch Patch\nEnd If\nEnd If &#039;InStr\nNext &#039;Patch\nIf NOT fMatchFound Then Log vbTab&amp;&quot;No match found for specified patch filter&quot;\nLoop While fPatchLoop\nNext &#039;iFoo\nEnd If &#039;InStr sProductCodes\nEnd If &#039;IsOfficeProduct\nNext &#039;Product\n\nIf iActivityCnt = 0 Then LogSummary &quot;RemovePatch&quot;,vbTab&amp;&quot;Nothing to remove for specified patch filter&quot;&amp;vbCrLf\n\nEnd Sub &#039;MspRemove\n&#039;=======================================================================================================\n\n&#039;Create and call an external script task to tweak the cached .msp\n&#039;This is required to work around the missing option in VBScript to release the database handle\nSub TweakDatabase(sMspFile)\n\nDim TweakDb\nDim sTweakCmd\n\nSet TweakDb = oFso.CreateTextFile(sTemp&amp;&quot;TweakDb.vbs&quot;,True,True)\nTweakDb.WriteLine &quot;On Error Resume Next&quot;\nTweakDb.WriteLine &quot;Set Msi = CreateObject(&quot;&amp;chr(34)&amp;&quot;WindowsInstaller.Installer&quot;&amp;chr(34)&amp;&quot;)&quot;\nTweakDb.WriteLine &quot;Set MspDb = Msi.OpenDatabase(&quot;&amp;chr(34)&amp;sMspFile&amp;chr(34)&amp;&quot;,&quot;&amp;MSIOPENDATABASEMODE_TRANSACT + MSIOPENDATABASEMODE_PATCHFILE&amp;&quot;)&quot;\nTweakDb.WriteLine &quot;Set ModifyView = MspDb.OpenView(&quot;&amp;chr(34)&amp;&quot;UPDATE `MsiPatchMetadata` SET `MsiPatchMetadata`.`Value`=&#039;1\u2032 WHERE `MsiPatchMetadata`.`Property`=&#039;AllowRemoval&#039;&quot;&amp;chr(34)&amp;&quot;)&quot;\nTweakDb.WriteLine &quot;ModifyView.Execute&quot;\nTweakDb.WriteLine &quot;ModifyView.Close&quot;\nTweakDb.WriteLine &quot;MspDb.Commit&quot;\nTweakDb.Close\nsTweakCmd = &quot;cscript &quot;&amp;chr(34)&amp;sTemp&amp;&quot;TweakDb.vbs&quot;&amp;chr(34)\noWShell.Run sTweakCmd, 0, True\noFso.DeleteFile sTemp&amp;&quot;TweakDb.vbs&quot;,True\nEnd Sub &#039;TweakDatabase\n&#039;=======================================================================================================\n\n&#039;Extracts the patch embedded .cab file to the %temp% folder\n&#039;and returns a string list of extracted cab files\nFunction CabExtract (sMspFile)\n\nDim MspDb,Record,File,CabFile,DataSize\nDim qView\nDim sCabList,sCabFile\n\nsCabList = &quot;&quot;\nIf NOT oFso.FileExists(sMspFile) Then\nwscript.echo &quot;File &#039;&quot;&amp;sMspFile&amp;&quot;&#039; does not exist.&quot;\nExit Function\nEnd If\nIf NOT LCase(Right(sMspFile,4))=&quot;.msp&quot; Then\nwscript.echo &quot;&#039;&quot;&amp;sMspFile&amp;&quot;&#039; is not a valid .msp file.&quot;\nExit Function\nEnd If\n\nSet File = oFso.GetFile(sMspFile)\nSet MspDb = oMsi.OpenDatabase(sMspFile,MSIOPENDATABASEMODE_PATCHFILE)\nSet qView = MspDb.OpenView(&quot;SELECT * FROM _Streams&quot;) : qView.Execute\nDo\nSet Record = qView.Fetch\nIf Record Is Nothing Then Exit Do\nIf InStr(UCase(Record.StringData(1)),&quot;_CAB&quot;)&gt;0 Then\nsCabFile = &quot;&quot; : sCabFile = Replace(File.Name,&quot;.msp&quot;,&quot;&quot;)&amp;&quot;_&quot;&amp;Record.StringData(1)&amp;&quot;.cab&quot;\nSet CabFile = oFso.CreateTextFile(sTemp&amp;sCabFile)\nCabFile.Write Record.ReadStream(2,Record.DataSize(2),MSIREADSTREAM_ANSI)\nCabFile.Close\nsCabList = &quot;;&quot;&amp;sTemp&amp;sCabFile\noWShell.Run chr(34) &amp;sTemp&amp;sCabFile&amp; chr(34)\nEnd If\nLoop\nqView.Close\nIf Len(sCabList)&gt;0 Then sCabList = Mid(sCabList,2)\nCabExtract = sCabList\n\nEnd Function &#039;CabExtract\n&#039;=======================================================================================================\n\n&#039;Module CollectUpdates\n\n&#039;=======================================================================================================\n\nSub CollectUpdates (sFilter)\n\nDim sUpdatesFolder, sMspPackageName\nDim Msp, PatchEx, PatchesEx\n\nConst PRODUCTCODE_EMPTY = &quot;&quot;\n\nsUpdatesFolder = oWShell.ExpandEnvironmentStrings(&quot;%TEMP%&quot;)&amp;&quot;\\Updates&quot;\nIf Not oFso.FolderExists(sTargetFolder) Then oFso.CreateFolder sUpdatesFolder\n\n&#039;Get all applied patches\nSet PatchesEx = oMsi.PatchesEx(PRODUCTCODE_EMPTY,USERSID_NULL,MSIINSTALLCONTEXT_MACHINE,MSIPATCHSTATE_APPLIED)\n\nOn Error Resume Next\n&#039;Enum the patches\nFor Each PatchEx in PatchesEx\nIf Not Err = 0 Then Err.Clear\n&#039;Connect to the patch file\nSet Msp = oMsi.OpenDatabase(PatchEx.PatchProperty(&quot;LocalPackage&quot;),MSIOPENDATABASEMODE_PATCHFILE)\nSet SumInfo = msp.SummaryInformation\nIf Err = 0 Then\nsMspPackageName = PatchEx.PatchProperty(&quot;LocalPackage&quot;)\nIf InStr(SumInfo.Property(PID_TEMPLATES),OFFICEID)&gt;0 Then\n&#039;Get the original patch name\nSet qView = msp.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;StdPackageName&#039;&quot;)\nqView.Execute : Set record = qView.Fetch()\n&#039;Copy and rename the patch to the original filename\noFso.CopyFile patch.PatchProperty(&quot;LocalPackage&quot;),sTargetFolder&amp;&quot;\\&quot;&amp;record.StringData(2),TRUE\nEnd If\nEnd If &#039;Err = 0\nNext &#039;patch\noWShell.Run &quot;explorer \/e,&quot;&amp;chr(34)&amp;sTargetFolder&amp;chr(34)\n\nEnd Sub &#039;CollectUpdates\n\n&#039;=======================================================================================================\n\n&#039;Module ViewPatch\n\n&#039;=======================================================================================================\n\n&#039;Show contents of .MSP files in an Excel workbook\n&#039;This requires XL to be installed\nSub ViewPatch (sMspFile)\n\nDim XlApp,XlWkbk,XlSheet,MspDb,File\nDim Element,Elements,Node,Record,qView,CompItem\n\nDim i,iSheetCnt,iRow,iRowCnt,iCol,iColCnt,iVersionMajor,iCnt\n\nDim MspTarget,sXml,sProductCode,sOFamilyVersion,sCompType,sCompFlt,sTargetVersion\nDim sTmpVersion,sRange,prod,sInstProds\n\nDim arrMspTargets,arrFileVersion,arrComputer,arrCompItem\n\nDim fOfficePatch,fOctPatch,fValidate\n\nOn Error Resume Next\n\nIf NOT XLInstalled Then \nwscript.echo &quot;This feature requires Excel to be installed. Cannot continue&quot;\nExit Sub\nEnd If\nIf NOT oFso.FileExists(sMspFile) Then\nwscript.echo &quot;File &#039;&quot;&amp;sMspFile&amp;&quot;&#039; does not exist.&quot;\nExit Sub\nEnd If\nIf NOT LCase(Right(sMspFile,4))=&quot;.msp&quot; Then\nwscript.echo &quot;&#039;&quot;&amp;sMspFile&amp;&quot;&#039; is not a valid .msp file.&quot;\nExit Sub\nEnd If\n\nSet File = oFso.GetFile(sMspFile)\n\nDim fNeedObsoleted,fNeedMstSubStorage,fSupersedesPrevious\n\nfOfficePatch = False\nfNeedObsoleted = False\nfNeedMstSubStorage = False\nfSupersedesPrevious = False\n\n&#039;Get the database handle\nSet MspDb = oMsi.OpenDatabase(sMspFile,MSIOPENDATABASEMODE_PATCHFILE)\nIf Not Err = 0 Then\nwscript.echo &quot;Could not open patch &quot;&amp;sMspFile\nErr.Clear\nExit Sub\nEnd If\n\n&#039;Determine if this is an Office Patch\narrMspTargets = Split(MspDb.SummaryInformation.Property(PID_TEMPLATE),&quot;;&quot;)\nFor Each MspTarget in arrMspTargets\nIf IsOfficeProduct(MspTarget) Then\nfOfficePatch = True\nExit For\nEnd If\nNext\n\nDim sPatchTables\nsPatchTables = GetDatabaseTables(MspDb)\n\n&#039;Get the PatchXml\nsXml = oMsi.ExtractPatchXMLData(sMspFile)\nXmlDoc.LoadXml(sXml)\n\n&#039;Create the XL instance\nSet XlApp = CreateObject(&quot;Excel.Application&quot;)\n\n&#039;Suppress XL alerts\nXlApp.DisplayAlerts = False\n\n&#039;Avoid blank worksheets\niSheetCnt = XlApp.SheetsInNewWorkbook\nXlApp.SheetsInNewWorkbook = 1\nSet XlWkbk = XlApp.Workbooks.Add\nXlApp.SheetsInNewWorkbook = iSheetCnt\n\n&#039;TmpStatus\n&#039;\u2014\u2014\u2014\nSet XlSheet = XlWkbk.Worksheets(1)\nXlSheet.Name = &quot;Status&quot;\nXlSheet.Cells(1, S_PROP).Value = &quot;Please wait&quot;\nXlSheet.Cells(1, S_VAL).Value = &quot;Collecting Patch Details \u2026&quot;\nXlSheet.Columns.Autofit\n\n&#039;Start the user UI experience\nXlApp.Interactive = False\nXlApp.Visible = True\nXlApp.WindowState = xlMaximized\nXlApp.ScreenUpdating = False\n\n&#039;Summary\n&#039;\u2014\u2014-\n&#039;Create a new workbook\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;Summary&quot;\n\nDim OctXml,sOctXmlFile,sRegXlOptions,fRegDelXmlSchemaAlert\n\niRow = 1 : iCol = 1\nXlSheet.Cells(1, S_PROP).Value = &quot;Property&quot;\nXlSheet.Cells(1, S_VAL).Value = &quot;Value&quot;\n\n&#039;FileName\nXlSheet.Cells(S_ROW_NAME, S_PROP).Value = &quot;FileName&quot;\nXlSheet.Cells(S_ROW_NAME, S_VAL).Value = File.Name\n&#039;KB\nDim sKB\nIf InStr(sPatchTables,&quot;MsiPatchMetadata&quot;)&gt;0 Then\nSet qView = MspDb.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;KBArticle Number&#039;&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\nsKB = UCase(Record.StringData(2))\nsKB = Replace(sKB,&quot;KB&quot;,&quot;&quot;)\nElse\nsKB = &quot;&quot;\nEnd If\nqView.Close\nEnd If\nIf sKB = &quot;&quot; Then\nDim iSiCnt,arrSi,sTitle,sSiTmp,sChar\n&#039;Scan the SummaryInformation data for the KB\nFor iSiCnt = 1 To 2\nSelect Case iSiCnt\nCase 1\narrSi = Split(MspDb.SummaryInformation.Property(PID_SUBJECT),&quot;;&quot;)\nCase 2\narrSi = Split(MspDb.SummaryInformation.Property(PID_TITLE),&quot;;&quot;)\nEnd Select\n\nIf IsArray(arrSi) Then\nFor Each sTitle in arrSi\nsSiTmp = &quot;&quot;\nsSiTmp = Replace(UCase(sTitle),&quot; &quot;,&quot;&quot;)\nIf InStr(sSiTmp,&quot;KB&quot;)&gt;0 Then\n&#039;Strip the KB\nsSiTmp = Mid(sSiTmp,InStr(sSiTmp,&quot;KB&quot;)+2)\nFor i = 1 To Len(sSiTmp)\nsChar = &quot;&quot;\nsChar = Mid(sSiTmp,i,1)\nIf (Asc(sChar) &gt;= 48 AND Asc(sChar) &lt;= 57) Then sKB=sKB&amp;sChar\nNext &#039;i\n&#039;Ensure a valid length\nIf Len(sKB)&lt;5 Then sKB=&quot;&quot; Else Exit For\nEnd If\nNext\nIf Len(sKB)&gt;4 Then Exit For\nEnd If &#039;IsArray(arrSi)\nNext &#039;iSiCnt\nEnd If\nXlSheet.Cells(S_ROW_KB, S_PROP).Value = &quot;KB&quot;\nIf Len(sKB)&gt;0 Then\nXlSheet.Cells(S_ROW_KB, S_VAL).Value = &quot;KB&quot;&amp;sKB\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_KB, S_VAL),&quot;http:\/\/support.microsoft.com\/kb\/&quot;&amp;sKB\nEnd If\n\n&#039;Packlet\nXlSheet.Cells(S_ROW_PACKLET, S_PROP).Value = &quot;Patch Packlet Family&quot;\nIf fOfficePatch Then\nDim arrPacklet,sPacklet\nsPacklet = &quot;&quot;\narrPacklet = Split(MspDb.SummaryInformation.Property(PID_TITLE),&quot;;&quot;)\nIf IsArray(arrPacklet) Then\nIf UBound(arrPacklet)&gt;0 Then\nsPacklet = arrPacklet(1)\nIf InStr(sPacklet,&quot;.&quot;)&gt;0 Then sPacklet = Left(sPacklet,InStr(sPacklet,&quot;.&quot;)-1)\nEnd If\nEnd If\nXlSheet.Cells(S_ROW_PACKLET, S_VAL).Value = sPacklet\nEnd If\n\n&#039;Sequence\nDim sSequence\nsSequence = &quot;&quot;\nXlSheet.Cells(S_ROW_SEQUENCE, S_PROP).Value = &quot;Sequence Number&quot;\nXlSheet.Cells(S_ROW_SEQUENCE, S_VAL).Value = &quot;&quot; &#039;Defer to MsiPatchSequence handling\n\n&#039;Baseline\nXlSheet.Cells(S_ROW_BASELINE, S_PROP).Value = &quot;Patch can be applied to build(s)&quot;\nXlSheet.Cells(S_ROW_BASELINE, S_VAL).Value = GetMspBldTargets(sXml)\n\n&#039;Prepare Supersedence field\nXlSheet.Cells(S_ROW_SUPERSEDENCE, S_PROP).Value = &quot;Supersedes previous patches&quot;\nXlSheet.Cells(S_ROW_SUPERSEDENCE, S_VAL).Value = &quot;No supersedence data available&quot;\n\n&#039;Prepare Uninstallable field\nXlSheet.Cells(S_ROW_UNINSTALLABLE, S_PROP).Value = &quot;Uninstallable&quot;\n\n&#039;SummaryInformation\nFor i = 1 To 19\nSelect Case i\nCase PID_TITLE\nXlSheet.Cells(S_ROW_TITLE, S_PROP).Value = &quot;Title&quot;\nXlSheet.Cells(S_ROW_TITLE, S_VAL).Value = MspDb.SummaryInformation.Property(PID_TITLE)\nCase PID_AUTHOR\nXlSheet.Cells(S_ROW_AUTHOR, S_PROP).Value = &quot;Author&quot;\nXlSheet.Cells(S_ROW_AUTHOR, S_VAL).Value = MspDb.SummaryInformation.Property(PID_AUTHOR)\nCase PID_SUBJECT\nXlSheet.Cells(S_ROW_SUBJECT, S_PROP).Value = &quot;Subject&quot;\nXlSheet.Cells(S_ROW_SUBJECT, S_VAL).Value = MspDb.SummaryInformation.Property(PID_SUBJECT)\nCase PID_COMMENTS\nXlSheet.Cells(S_ROW_COMMENTS, S_PROP).Value = &quot;Comments&quot;\nXlSheet.Cells(S_ROW_COMMENTS, S_VAL).Value = MspDb.SummaryInformation.Property(PID_COMMENTS)&amp;vbCrLf\nCase PID_REVNUMBER &#039;PatchCode &amp; Obsoletion\nDim sPatchCode\nXlSheet.Cells(S_ROW_PATCHCODE, S_PROP).Value = &quot;PatchCode&quot;\nXlSheet.Cells(S_ROW_OBSOLETES, S_PROP).Value = &quot;Obsoletes&quot;\nsPatchCode = MspDb.SummaryInformation.Property(PID_REVNUMBER) &#039;PatchCode\nIf Len(sPatchCode)&gt;LEN_GUID Then\nXlSheet.Cells(S_ROW_OBSOLETES, S_VAL).Value = &quot;See ObsoletedPatches Sheet&quot;\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_OBSOLETES, S_VAL),&quot;&quot;,&quot;ObsoletedPatches!$A$1&quot;\nfNeedObsoleted = True\nsPatchCode=Left(sPatchCode,LEN_GUID)\nEnd If\nXlSheet.Cells(S_ROW_PATCHCODE, S_VAL).Value = sPatchCode\nCase PID_TEMPLATE &#039;Targets\nXlSheet.Cells(S_ROW_TARGETS, S_PROP).Value = &quot;Targets&quot;\nXlSheet.Cells(S_ROW_TARGETS, S_VAL).Value = &quot;See PatchTargets Sheet&quot;\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_TARGETS, S_VAL),&quot;&quot;,&quot;PatchTargets!$A$1&quot;\nCase PID_LASTAUTHOR &#039;List of mst substorages\nXlSheet.Cells(S_ROW_TRANSFORMSUB, S_PROP).Value = &quot;Transform Substorages&quot;\nXlSheet.Cells(S_ROW_TRANSFORMSUB, S_VAL).Value = &quot;See TransformSubStorages Sheet&quot;\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_TRANSFORMSUB, S_VAL),&quot;&quot;,&quot;TransformSubStorages!$A$1&quot;\nfNeedMstSubStorage = True\nCase PID_WORDCOUNT &#039;Required WI version\nXlSheet.Cells(S_ROW_PATCHTYPE, S_PROP).Value = &quot;WI Version Required&quot;\nSelect Case MspDb.SummaryInformation.Property(PID_WORDCOUNT)\nCase 1\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = &quot;1.0 (Type 1) &quot;\nCase 2\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = &quot;1.2 (Type 2)&quot;\nCase 3\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = &quot;2.0 (Type 3)&quot;\nCase 4\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = &quot;3.0 (Type 4)&quot;\nCase 5\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = &quot;3.1 (Type 5)&quot;\nCase Else\nXlSheet.Cells(S_ROW_PATCHTYPE, S_VAL).Value = MspDb.SummaryInformation.Property(PID_WORDCOUNT)\nEnd Select\nCase PID_SECURITY &#039;Read Only Flag\nXlSheet.Cells(S_ROW_SECURITY, S_PROP).Value = &quot;Read-Only Security&quot;\nSelect Case MspDb.SummaryInformation.Property(PID_SECURITY)\nCase 0\nXlSheet.Cells(S_ROW_SECURITY, S_VAL).Value = &quot;No Restriction&quot;\nCase 2\nXlSheet.Cells(S_ROW_SECURITY, S_VAL).Value = &quot;Read-only recommended&quot;\nCase 4\nXlSheet.Cells(S_ROW_SECURITY, S_VAL).Value = &quot;Read-only enforced&quot;\nCase Else\nXlSheet.Cells(S_ROW_SECURITY, S_VAL).Value = MspDb.SummaryInformation.Property(PID_SECURITY)\nEnd Select\nCase Else\n&#039;Do Not List\nEnd Select\nNext &#039;i\n\n&#039;PatchXml\nDim PatchXml\nSet PatchXml = oFso.CreateTextFile(sTemp&amp;File.Name&amp;&quot;_Patch.xml&quot;,True,True)\nPatchXml.Write sXml\nPatchXml.Close\nXlSheet.Cells(S_ROW_PATCHXML, S_PROP).Value = &quot;PatchXML&quot;\nXlSheet.Cells(S_ROW_PATCHXML, S_VAL).Value = sTemp&amp;File.Name&amp;&quot;_Patch.xml&quot;\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_PATCHXML, S_VAL),sTemp&amp;File.Name&amp;&quot;_Patch.xml&quot;\n\n&#039;OCT PatchXml\nSet qView = MspDb.OpenView(&quot;SELECT * FROM _Streams&quot;) : qView.Execute\ni = 0\nDo\nSet Record = qView.Fetch\nIf Record Is Nothing Then Exit Do\nIf InStr(UCase(Record.StringData(1)),&quot;METADATA&quot;)&gt;0 Then\ni = i + 1\nsOctXmlFile = &quot;&quot; : sOctXmlFile = sTemp&amp;File.Name&amp;&quot;_OCT_&quot;&amp;Record.StringData(1)&amp;&quot;.xml&quot;\nSet OctXml = oFso.CreateTextFile(sOctXmlFile)\nOctXml.Write Record.ReadStream(2,Record.DataSize(2),MSIREADSTREAM_ANSI)\nOctXml.Close\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow, S_PROP).Value = &quot;OCT Metadata&quot;\nXlSheet.Cells(iRow, S_VAL).Value = sOctXmlFile\nXlSheet.Hyperlinks.Add XlSheet.Cells(iRow, S_VAL),sOctXmlFile\nIf IsBaselineRequired(&quot;&quot;,sXml) Then \niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow, S_PROP).Value = &quot;IMPORTANT NOTE&quot;\nXlSheet.Cells(iRow, S_VAL).Value = &quot;This is a customization patch based on the original release of the OCT.&quot;&amp;vbCrLf&amp;&quot;A more recent OCT version is available from http:\/\/www.microsoft.com\/downloads\/details.aspx?displaylang=en&amp;FamilyID=73d955c0-da87-4bc2-bbf6-260e700519a8&#8221;&quot;\nEnd If\nEnd If\nLoop\nqView.Close\n&#039;Transform SubStorages\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\nIf fNeedMstSubStorage Then\nDim arrMstSubStore,dicTransformRow\n\nSet dicTransformRow = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;Create the TransformSubStorages sheet\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;TransformSubStorages&quot;\nXlSheet.Move ,XlWkbk.Sheets(&quot;Summary&quot;)\n\n&#039;Fill the sheet\niRow = 1 : iCol = 1\nXlSheet.Cells(HROW, COL_NONPOUND).Value = &quot;Non Pound Transform&quot;\nXlSheet.Cells(HROW, COL_POUND).Value = &quot;(Patch Specific Tables)&quot;\narrMstSubStore = Split(MspDb.SummaryInformation.Property(PID_LASTAUTHOR),&quot;;&quot;)\nFor i = 0 To UBound(arrMstSubStore)\nIf InStr(arrMstSubStore(i),&quot;#&quot;)&gt;0 Then iCol = COL_POUND Else iCol = COL_NONPOUND\niRow = Int((i+4)\/2)\nXlSheet.Cells(iRow, iCol).Value = arrMstSubStore(i)\nIf NOT dicTransformRow.Exists(arrMstSubStore(i)) Then dicTransformRow.Add arrMstSubStore(i),iRow\nNext &#039;i\nIf fOfficePatch Then ExtendTransformTable XlSheet,dicTransformRow,MspDb\nEnd If\n\n&#039;Obsoleted Patches\n&#039;\u2014\u2014\u2014\u2014\u2014-\nIf fNeedObsoleted Then\nDim arrObsoleted\n&#039;Create the Obsoleted sheet\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;ObsoletedPatches&quot;\nXlSheet.Move ,XlWkbk.Sheets(&quot;Summary&quot;)\n\n&#039;Fill the sheet\niRow = 1 : iCol = 1\nXlSheet.Cells(HROW, iCol).Value = &quot;Obsoleted Patches&quot;\narrObsoleted = Split(Mid(MspDb.SummaryInformation.Property(PID_REVNUMBER),LEN_GUID+1),&quot;}&quot;)\nFor i = 0 To UBound(arrObsoleted)-1\nXlSheet.Cells(i+2, iCol).Value = arrObsoleted(i)&amp;&quot;}&quot;\nNext &#039;i\nEnd If\n\n&#039;MsiPatchSequence\n&#039;\u2014\u2014\u2014\u2014\u2014-\nIf InStr(UCase(sPatchTables),&quot;MSIPATCHSEQUENCE&quot;)&gt;0 Then\n\nfSupersedesPrevious = False\n\n&#039;Create the MsiPatchSequence sheet\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;MsiPatchSequence&quot;\nXlSheet.Move ,XlWkbk.Sheets(&quot;Summary&quot;)\n\n&#039;Fill the sheet\niRow = 1 : iCol = 1\nXlSheet.Cells(HROW, SEQ_PATCHFAMILY).Value = &quot;PatchFamily&quot;\nXlSheet.Cells(HROW, SEQ_PRODUCTCODE).Value = &quot;ProductCode&quot;\nXlSheet.Cells(HROW, SEQ_SEQUENCE).Value = &quot;Sequence&quot;\nXlSheet.Cells(HROW, SEQ_ATTRIBUTE).Value = &quot;(Supersedence Flag)&quot;\n\nSet qView = MspDb.OpenView(&quot;SELECT * FROM MsiPatchSequence&quot;) : qView.Execute\nSet Record = qView.Fetch()\nDo Until Record Is Nothing\niRow = iRow + 1\nXlSheet.Cells(iRow, SEQ_PATCHFAMILY).Value = Record.StringData(SEQ_PATCHFAMILY)\nXlSheet.Cells(iRow, SEQ_PRODUCTCODE).Value = Record.StringData(SEQ_PRODUCTCODE)\nXlSheet.Cells(iRow, SEQ_SEQUENCE).Value = Record.StringData(SEQ_SEQUENCE)\nIf NOT InStr(sSequence,Record.StringData(SEQ_SEQUENCE))&gt;0 Then sSequence = &quot;;&quot;&amp;Record.StringData(SEQ_SEQUENCE)\nXlSheet.Cells(iRow, SEQ_ATTRIBUTE).Value = Record.StringData(SEQ_ATTRIBUTE)\nfSupersedesPrevious = fSupersedesPrevious OR Record.StringData(SEQ_ATTRIBUTE)=&quot;1&quot;\nSet Record = qView.Fetch()\nLoop\nqView.Close\nIf Len(sSequence)&gt;1 Then sSequence = Mid(sSequence,2)\n\n&#039;Update Supersedence field on Summary Sheet\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\nIf fSupersedesPrevious Then XlSheet.Cells(S_ROW_SUPERSEDENCE, S_VAL).Value = &quot;Yes&quot; Else XlSheet.Cells(S_ROW_SUPERSEDENCE, S_VAL).Value = &quot;No&quot;\nEnd If &#039;MsiPatchSequence\n\n&#039;Add Sequence data to summary sheet\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\nIf InStr(sSequence,&quot;;&quot;)&gt;0 Then \nXlSheet.Cells(S_ROW_SEQUENCE, S_VAL).Value = &quot;Mulitple sequence data available&quot;\nXlSheet.Hyperlinks.Add XlSheet.Cells(S_ROW_SEQUENCE, S_VAL),&quot;&quot;,&quot;MsiPatchSequence!$A$1&quot;\nElse\nIf sSequence = &quot;&quot; Then\nIf fOfficePatch Then sSequence = GetLegacyMspSeq(MspDb)\nEnd If\nIf sSequence = &quot;&quot; Then sSequence = &quot;No sequence data available&quot;\nXlSheet.Cells(S_ROW_SEQUENCE, S_VAL).Value = sSequence\nEnd If\n\n&#039;MsiPatchMetaData\n&#039;\u2014\u2014\u2014\u2014\u2014-\nIf InStr(UCase(sPatchTables),&quot;MSIPATCHMETADATA&quot;)&gt;0 Then\n\n&#039;Create the MsiPatchMetaData sheet\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;MsiPatchMetaData&quot;\nXlSheet.Move ,XlWkbk.Sheets(&quot;Summary&quot;)\n\n&#039;Fill the sheet\niRow = 1 : iCol = 1\nXlSheet.Cells(HROW, MET_COMPANY).Value = &quot;Company&quot;\nXlSheet.Cells(HROW, MET_PROPERTY).Value = &quot;Property&quot;\nXlSheet.Cells(HROW, MET_VALUE).Value = &quot;Value&quot;\n\nSet qView = MspDb.OpenView(&quot;SELECT * FROM MsiPatchMetadata&quot;) : qView.Execute\nSet Record = qView.Fetch()\nDo Until Record Is Nothing\niRow = iRow + 1\nXlSheet.Cells(iRow, MET_COMPANY).Value = Record.StringData(MET_COMPANY)\nXlSheet.Cells(iRow, MET_PROPERTY).Value = Record.StringData(MET_PROPERTY)\nXlSheet.Cells(iRow, MET_VALUE).Value = Record.StringData(MET_VALUE)\nIf UCase(Record.StringData(MET_PROPERTY)) = &quot;STDPACKAGENAME&quot; AND fOfficePatch Then\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\nsTmp = &quot;&quot;\nsTmp = Record.StringData(MET_VALUE)\nIf InStr(sTmp,&quot;.&quot;)&gt;0 Then sTmp = Left(sTmp,InStr(sTmp,&quot;.&quot;)-1)\nIf NOT sTmp = &quot;&quot; Then XlSheet.Cells(S_ROW_PACKLET, S_VAL).Value = sTmp\nSet XlSheet = XlWkbk.Sheets(&quot;MsiPatchMetaData&quot;)\nEnd If\nIf UCase(Record.StringData(MET_PROPERTY)) = &quot;ALLOWREMOVAL&quot; Then\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\nIf Record.StringData(MET_VALUE) = 1 Then sTmp = &quot;Yes&quot; Else sTmp = &quot;No&quot;\nXlSheet.Cells(S_ROW_UNINSTALLABLE, S_VAL).Value = sTmp\nSet XlSheet = XlWkbk.Sheets(&quot;MsiPatchMetaData&quot;)\nEnd If\nSet Record = qView.Fetch()\nLoop\nqView.Close\nEnd If &#039;MsiPatchMetaData\n\n&#039;PatchTargets\n&#039;\u2014\u2014\u2014\u2014\n\nDim iTargetGuid,iTargetVer,iUpdatedVer,iLcid,iCulture,iVTargetProd,iVTargetVer,iVTargetLang,iVTargetUpg\n\n&#039;Create the PatchTarget sheet\n&#039;- - - - - - - - - - - - - - \nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;PatchTargets&quot;\nXlSheet.Move ,XlWkbk.Sheets(&quot;Summary&quot;)\niRow = 1 : iCol = 1\n\nIf fOfficePatch Then\niTargetGuid = OTARGETGUID\niTargetVer = OMSPTARGETVER\niUpdatedVer = OMSPUPDATEDVER\niLcid = OLCID\niCulture = OCULTURE\niVTargetProd = OVAL_TARGETPRODUCTCODE\niVTargetVer = OVAL_TARGETVERSION\niVTargetLang = OVAL_TARGETLANGUAGE\niVTargetUpg = OVAL_TARGETUPGRADECODE\nXlSheet.Cells(HROW, OTARGETNAME).Value = &quot;ProductName&quot;\nXlSheet.Cells(HROW, OFAMILYVER).Value = &quot;Office Family&quot;\nXlSheet.Cells(HROW, OLICENSE).Value = &quot;License&quot;\nXlSheet.Cells(HROW, OARCHITECTURE).Value = &quot;Platform&quot;\nElse\niTargetGuid = TARGETGUID\niTargetVer = MSPTARGETVER\niUpdatedVer = MSPUPDATEDVER\niLcid = LCID\niCulture = CULTURE\niVTargetProd = VAL_TARGETPRODUCTCODE\niVTargetVer = VAL_TARGETVERSION\niVTargetLang = VAL_TARGETLANGUAGE\niVTargetUpg = VAL_TARGETUPGRADECODE\nEnd If\nXlSheet.Cells(HROW, iTargetGuid).Value = &quot;ProductCode&quot;\nXlSheet.Cells(HROW, iTargetVer).Value = &quot;Target Baseline&quot;\nXlSheet.Cells(HROW, iUpdatedVer).Value = &quot;Updated Baseline&quot;\nXlSheet.Cells(HROW, iLcid).Value = &quot;LCID&quot;\nXlSheet.Cells(HROW, iCulture).Value = &quot;Culture&quot;\nXlSheet.Cells(HROW, iVTargetProd).Value = &quot;Validate ProductCode&quot;\nXlSheet.Cells(HROW, iVTargetVer).Value = &quot;Validate ProductVersion&quot;\nXlSheet.Cells(HROW, iVTargetLang).Value = &quot;Validate Language&quot;\nXlSheet.Cells(HROW, iVTargetUpg).Value = &quot;Validate UpgradeCode&quot;\nIf fOfficePatch Then\nXlSheet.Cells(HROW, iTargetVer).Value = &quot;Required Office Build&quot;\nXlSheet.Cells(HROW, iUpdatedVer).Value = &quot;Office Build Version After Patch&quot;\nEnd If\n&#039;Fill data into the PatchTargets sheet\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\niRow = iRow + 1\nsProductCode = Element.selectSingleNode(&quot;TargetProductCode&quot;).text\nXlSheet.Cells(iRow, iTargetGuid).Value = sProductCode\nIf NOT dicProdMst.Exists(sProductCode) Then dicProdMst.Add sProductCode,XlWkbk.Sheets(&quot;TransformSubStorages&quot;).Cells(iRow,COL_NONPOUND)\nIf fOfficePatch Then\niVersionMajor = GetVersionMajor(sProductCode)\n&#039;ProductName\nSelect Case iVersionMajor\nCase 9 \nsOFamilyVersion = 2000\nXlSheet.Cells(iRow, OTARGETNAME).Value = GetProductID(Mid(sProductCode,4,2),sOFamilyVersion)\nCase 10\nsOFamilyVersion = 2002\nXlSheet.Cells(iRow, OTARGETNAME).Value = GetProductID(Mid(sProductCode,4,2),sOFamilyVersion)\nCase 11 \nsOFamilyVersion = 2003\nXlSheet.Cells(iRow, OTARGETNAME).Value = GetProductID(Mid(sProductCode,4,2),sOFamilyVersion)\nCase 12\nsOFamilyVersion = 2007\nXlSheet.Cells(iRow, OTARGETNAME).Value = GetProductID(Mid(sProductCode,11,4),sOFamilyVersion)\nCase 14\nsOFamilyVersion = 2010\nXlSheet.Cells(iRow, OTARGETNAME).Value = GetProductID(Mid(sProductCode,11,4),sOFamilyVersion)\nCase Else\nsOFamilyVersion = &quot;&quot;\nEnd Select\nXlSheet.Cells(iRow, OFAMILYVER).Value = sOFamilyVersion\nXlSheet.Cells(iRow, OLICENSE).Value = GetReleaseType(CInt(Mid(sProductCode,3,1)))\nIf Mid(sProductCode,21,1) = &quot;1&quot; Then XlSheet.Cells(iRow, OARCHITECTURE).Value = &quot;x64&quot; Else XlSheet.Cells(iRow, OARCHITECTURE).Value = &quot;x86&quot;\nEnd If\n\nFor Each Node in Element.ChildNodes\nSelect Case Node.NodeName\nCase &quot;TargetProductCode&quot;\nXlSheet.Cells(iRow, iVTargetProd).Value = CBool(Node.getAttribute(&quot;Validate&quot;))\nCase &quot;TargetVersion&quot; \nfValidate = CBool(Node.getAttribute(&quot;Validate&quot;))\nsTargetVersion = Element.selectSingleNode(&quot;TargetVersion&quot;).text\nIf fValidate Then\nsCompFlt = Node.getAttribute(&quot;ComparisonFilter&quot;)\nsCompType = Node.getAttribute(&quot;ComparisonType&quot;)\narrFileVersion = Split(sTargetVersion,&quot;.&quot;)\n&#039;Set the filter setting\nSelect Case sCompFlt\nCase &quot;None&quot;\niCnt = -1\nCase &quot;Major&quot;\niCnt = 0\nCase &quot;MajorMinor&quot;\niCnt = 1\nCase &quot;MajorMinorUpdate&quot;\niCnt = 2\nCase Else\nEnd Select\nsTmpVersion = &quot;&quot;\nIf iCnt &gt; -1 Then\nFor i = 0 To iCnt\nsTmpVersion = sTmpVersion&amp;&quot;.&quot;&amp;arrFileVersion(i)\nNext &#039;i\nsTmpVersion = Mid(sTmpVersion,2)\nElse\nsTmpVersion = &quot;None&quot;\nEnd If\n&#039;XlSheet.Cells(iRow, iTargetVer).Value = sTmpVersion&amp;&quot; (&quot;&amp;sTargetVersion&amp;&quot;)&quot;\nXlSheet.Cells(iRow, iTargetVer).Value = sTargetVersion\nXlSheet.Cells(iRow, iVTargetVer).Value = &quot;TRUE: &quot;&amp;sCompType&amp;&quot; &quot;&amp;sTmpVersion\nElse\nXlSheet.Cells(iRow, iTargetVer).Value = &quot;Baselineless (&quot;&amp;sTargetVersion&amp;&quot;)&quot;\nXlSheet.Cells(iRow, iVTargetVer).Value = &quot;FALSE&quot;\nEnd If\n&#039;If this is an Office patch, add the SP Level\nIf fOfficePatch Then XlSheet.Cells(iRow, iTargetVer).Value = XlSheet.Cells(iRow, iTargetVer).Value &amp; GetSpLevel(sTargetVersion)\nCase &quot;TargetLanguage&quot; \nXlSheet.Cells(iRow, iLcid).Value = Element.selectSingleNode(&quot;TargetLanguage&quot;).text\nXlSheet.Cells(iRow, iCulture).Value = GetCultureInfo(XlSheet.Cells(iRow, iLcid).Value)\nXlSheet.Cells(iRow, iVTargetLang).Value = CBool(Node.getAttribute(&quot;Validate&quot;))\nCase &quot;UpdatedVersion&quot; \nXlSheet.Cells(iRow, iUpdatedVer).Value = Element.selectSingleNode(&quot;UpdatedVersion&quot;).text &amp; GetSpLevel(Element.selectSingleNode(&quot;UpdatedVersion&quot;).text)\nCase &quot;UpgradeCode&quot;\nfValidate = CBool(Node.getAttribute(&quot;Validate&quot;))\nIf fValidate Then\nXlSheet.Cells(iRow, iVTargetUpg).Value = &quot;TRUE: &quot;&amp;Element.selectSingleNode(&quot;UpgradeCode&quot;).text\nElse\nXlSheet.Cells(iRow, iVTargetUpg).Value = &quot;FALSE&quot;\nEnd If\nCase Else\nEnd Select\nNext &#039;Node\nIf NOT Len(XlSheet.Cells(iRow, iUpdatedVer).Value)&gt;0 Then XlSheet.Cells(iRow, iUpdatedVer).Value = &quot;Not Updated (&quot;&amp;sTargetVersion&amp;&quot;)&quot;&amp; GetSpLevel(sTargetVersion)\n\nNext &#039;Element\n\n&#039;CustomAction Table\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\n\n&#039;File and other tables\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;File_Table&quot;\nXlSheet.Move ,XlWkbk.Sheets(XlWkbk.Sheets.Count)\niRow = 1 : iCol = 1\n\nDim dicMspFiles\nSet dicMspFiles = CreateObject(&quot;Scripting.Dictionary&quot;)\nFillTables XlWkbk,XlSheet,MspDb,sMspFile,dicMspFiles,fOfficePatch,XmlDoc\nAddMspHashes XlWkbk\nIf fMsiProvidedAsFile Then AddFileNames XlWkbk\n\n&#039;Per Product Detection (DeepScan)\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\n\nFor Each prod in oMsi.Products\nsInstProds = sinstProds &amp; &quot;;&quot; &amp; prod\nNext\n\nfDeepScan = False\nFor Each MspTarget in arrMspTargets\nIf InStr(sInstProds,MspTarget)&gt;0 Then\nIf IsOfficeProduct(MspTarget) Then\nfDeepScan = True\nExit For\nEnd If\nEnd If\nNext\n\n&#039;PredictedAction, ComponentState, ComponentClients, FilePath, CurrentVersion\nIf fDeepScan Then\n\n&#039;Run the full patch detection\nApplyPatches\n\n&#039;Refresh the status screen\nSet XlSheet = XlWkbk.Sheets(&quot;Status&quot;)\nXlSheet.Activate\nXlSheet.Cells(1, S_VAL).Value = &quot;DeepScan in progress \u2026&quot;\nXlApp.Interactive = False\nXlApp.ScreenUpdating = True\nXlApp.ScreenUpdating = False\n\nComputerProperties\nSet XlSheet = XlWkbk.Sheets(&quot;File_Table&quot;)\nMspDeepScan MspDb,dicMspFiles,XlSheet\n\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = &quot;Computer&quot;\nXlSheet.Move XlWkbk.Sheets(&quot;Summary&quot;)\nXlSheet.Cells(HROW,S_PROP).Value = &quot;Property&quot;\nXlSheet.Cells(HROW,S_VAL).Value = &quot;Value&quot;\n\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;ComputerName&quot;\nXlSheet.Cells(iRow,S_VAL).Value = sComputerName\n\narrComputer = Split(sOSinfo,&quot;,&quot;)\nFor Each CompItem in arrComputer\narrCompItem = Split(CompItem,&quot;: &quot;)\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = arrCompItem(0)\nXlSheet.Cells(iRow,S_VAL).Value = arrCompItem(1)\nNext\n\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;Windows Installer Version&quot;\nXlSheet.Cells(iRow,S_VAL).Value = vWI\n\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;)\nFor iIndex = 0 To UBound(arrSUpdatesAll)\nIf UCase(arrSUpdatesAll(iIndex,COL_FILENAME)) = UCase(sMspFile) Then\n&#039;Patch is applicable to\nIf InStr(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),&quot;;&quot;)&gt;0 Then\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;Patch is applicable to&quot;\nReDim arrTmpLog(-1)\narrSUpdatesAll(iIndex,COL_APPLICABLECNT) = Left(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),Len(arrSUpdatesAll(iIndex,COL_APPLICABLECNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_APPLICABLECNT),&quot;;&quot;)\nsTmp = &quot;&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)&amp;&quot; - &quot;&amp;oMsi.ProductInfo(LogProd,&quot;VersionString&quot;)&amp;vbCrLf\nNext &#039;\nXlSheet.Cells(iRow,S_VAL).Value = sTmp\nEnd If\n\n&#039;Patch already applied to\nIf InStr(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),&quot;;&quot;)&gt;0 Then\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;Patch is already applied to&quot;\nReDim arrTmpLog(-1)\narrSUpdatesAll(iIndex,COL_APPLIEDCNT) = Left(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),Len(arrSUpdatesAll(iIndex,COL_APPLIEDCNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_APPLIEDCNT),&quot;;&quot;)\nsTmp = &quot;&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)&amp;&quot; - &quot;&amp;oMsi.ProductInfo(LogProd,&quot;VersionString&quot;)&amp;vbCrLf\nNext &#039;\nXlSheet.Cells(iRow,S_VAL).Value = sTmp\nEnd If\n\n&#039;Patch is superseded for\nIf InStr(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),&quot;;&quot;)&gt;0 Then\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;Patch is superseded for&quot;\nReDim arrTmpLog(-1)\narrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT) = Left(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),Len(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_SUPERSEDEDCNT),&quot;;&quot;)\nsTmp = &quot;&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)&amp;&quot; - &quot;&amp;oMsi.ProductInfo(LogProd,&quot;VersionString&quot;)&amp;vbCrLf\nNext &#039;\nXlSheet.Cells(iRow,S_VAL).Value = sTmp\nEnd If\n\n&#039;Patch not applicable to\nIf InStr(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),&quot;;&quot;)&gt;0 Then\niRow = XlSheet.Columns(1).CurrentRegion.Rows.Count +1\nXlSheet.Cells(iRow,S_PROP).Value = &quot;Patch is not applicable to&quot;\nReDim arrTmpLog(-1)\narrSUpdatesAll(iIndex,COL_NOQALBASELINECNT) = Left(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),Len(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT))-1)\narrTmpLog = Split(arrSUpdatesAll(iIndex,COL_NOQALBASELINECNT),&quot;;&quot;)\nsTmp = &quot;&quot;\nFor Each LogProd in arrTmpLog\nsTmp = sTmp&amp;oMsi.ProductInfo(LogProd,&quot;ProductName&quot;)&amp;&quot; - &quot;&amp;oMsi.ProductInfo(LogProd,&quot;VersionString&quot;)&amp;vbCrLf\nNext &#039;\nXlSheet.Cells(iRow,S_VAL).Value = sTmp\nEnd If\nEnd If\nNext &#039;iIndex\n\nEnd If\n\n&#039;Final cleanups\nXlWkbk.BuiltinDocumentProperties(1) = SOLUTIONNAME&amp;&quot; v&quot;&amp;SCRIPTBUILD\n\nXlWkbk.Sheets(&quot;Status&quot;).Delete\nMakeWkbkLookPretty(XlWkbk)\nSet XlSheet = XlWkbk.Sheets(&quot;Summary&quot;) \nIf NOT fOfficePatch Then XlSheet.Rows(S_ROW_PACKLET).Delete xlUp\n\n&#039;Hand over XL control to the user\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nXlWkbk.Worksheets(&quot;Summary&quot;).Activate\nXlApp.UserControl = True\nXlApp.ScreenUpdating = True\nXlApp.Interactive = True\nXlWkbk.Saved = True\nXlApp.DisplayAlerts = True\noWShell.AppActivate XlApp.Name&amp;&quot; - &quot;&amp;XlWkBk.Name\nSet XlApp = Nothing\n\nEnd Sub &#039;ViewPatch\n&#039;=======================================================================================================\nSub MakeWkbkLookPretty(XlWkbk)\n\nDim XlSheet\nDim sRange,sSheetName,sGuid,sTName\nDim iRowCnt,iColCnt\n\nOn Error Resume Next\n\nFor Each XlSheet in XlWkbk.Sheets\nXlSheet.Columns.Autofit\nXlSheet.Rows.Autofit\nsSheetName = XlSheet.Name\nIf InStr(sSheetName,&quot;}&quot;)&gt;0 Then\nsGuid = Left(sSheetName,InStr(sSheetName,&quot;}&quot;))\nsSheetName = Mid(sSheetName,InStr(sSheetName,&quot;}&quot;)+2)\nElse\nsGuid = &quot;&quot;\nEnd If\nSelect Case sSheetName\nCase &quot;Computer&quot;\nsTName = &quot;TableComputer&quot; &amp; sGuid\nXlSheet.Columns(1).VerticalAlignment = xlTop\nXlSheet.Columns(2).NumberFormat = &quot;@&quot;\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).ShowTableStyleFirstColumn = True\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium10&quot;\nCase &quot;Summary&quot;\nsTName = &quot;TableSummary&quot; &amp; sGuid\nXlSheet.Columns(1).VerticalAlignment = xlTop\nXlSheet.Columns(2).NumberFormat = &quot;@&quot;\nXlSheet.Cells(S_ROW_BASELINE, S_PROP).NumberFormat = &quot;0.00&quot;\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).ShowTableStyleFirstColumn = True\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium10&quot;\nCase &quot;TransformSubStorages&quot;\nsTName = &quot;TableSubStore&quot; &amp; sGuid\nXlSheet.Columns(1).VerticalAlignment = xlTop\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium4&quot;\nXlSheet.Cells(HROW, COL_NONPOUND).Value = &quot;Non Pound Transform&quot;&amp;vbCrLf&amp;&quot;(Database Diff)&quot;\nXlSheet.Cells(HROW, COL_POUND).Value = &quot;Pound Transform&quot;&amp;vbCrLf&amp;&quot;(Patch Specific Tables)&quot;\nCase &quot;ObsoletedPatches&quot;\nsTName = &quot;TableObsoleted&quot; &amp; sGuid\nXlSheet.Columns(1).VerticalAlignment = xlTop\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium4&quot;\nCase &quot;MsiPatchSequence&quot;\nsTName = &quot;TableMsiPatchSequence&quot; &amp; sGuid\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium4&quot;\nXlSheet.Columns.Autofit\nXlSheet.Cells(HROW, SEQ_ATTRIBUTE).Value = &quot;Attribute&quot;&amp;vbCrLf&amp;&quot;(Supersedence Flag)&quot;\nXlSheet.Rows.Autofit\nCase &quot;MsiPatchMetaData&quot;\nsTName = &quot;TableMsiPatchMetaData&quot; &amp; sGuid\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium4&quot;\nCase &quot;PatchTargets&quot;\nsTName = &quot;TablePatchTargets&quot; &amp; sGuid\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium4&quot;\nCase &quot;File_Table&quot;\nsTName = &quot;TableFiles&quot; &amp; sGuid\niRowCnt = XlSheet.Cells(1, 1).CurrentRegion.Rows.Count\nsRange = &quot;$A$1:&quot;&amp;XlSheet.Cells(iRowCnt,F_SEQUENCE).Address\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.Range(sRange), , xlYes).Name = sTName\nXlSheet.ListObjects(sTName).TableStyle = &quot;TableStyleMedium2&quot;\nIf fDeepScan Then\nsRange = XLSheet.Cells(1,F_PREDICTED).Address&amp;&quot;:&quot;&amp;XLSheet.Cells(iRowCnt,F_FILEPATH).Address\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.Range(sRange), , xlYes).Name = &quot;ThisComputerFiles&quot;\nXlSheet.ListObjects(&quot;ThisComputerFiles&quot;).TableStyle = &quot;TableStyleMedium4&quot;\nEnd If &#039;fDeepScan\nCase Else\nXlSheet.ListObjects.Add(xlSrcRange, XlSheet.UsedRange, , xlYes).Name = XlSheet.Name\nXlSheet.ListObjects(XlSheet.Name).TableStyle = &quot;TableStyleMedium2&quot;\nEnd Select\nNext &#039;XlSheet\n\nEnd Sub &#039;MakeWkbkLookPretty\n&#039;=======================================================================================================\n\nSub MspDeepScan (MspDb,dicMspFile,XlSheet)\n\nDim Prod,Session,SessionDb,Record,Component,File\nDim sPatchTargets,sTargetPath,sFileName,sComponent,sComponentId,sComponentClients\nDim sDirectory,CompClient,Ftk,sFullFileName,sKeyPath,sVersionKeyPath,sCurVer,sLfn\nDim fFound,fAllFound,fCompLocal,fKeyPathLow,fPatchFileVersionLow,fCopyPatchFile\nDim fUserModified,fFileNotFound\nDim dicDirectory,dicFtkFile,dicAssembly,dicTables,dicFtkComp,dicCompDir,dicCompComp\nDim dicComponents,dicKeypathConflict\nDim qView\nDim iRow,iCmp\n\nfAllFound = False\nfFound = False\noMsi.UILevel = 2 &#039;None\n\nSet dicDirectory = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicTables = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicAssembly = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicFtkComp = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicFtkFile = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicCompDir = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicCompComp = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicKeypathConflict = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicComponents = CreateObject(&quot;Scripting.Dictionary&quot;)\n\n&#039;Get the PatchTargets\nsPatchTargets = MspDb.SummaryInformation.Property(PID_TEMPLATE)\n\n&#039;Enum installed clients to build the reference dictionaries\nFor Each Prod in oMsi.Products\nIf InStr(sPatchTargets,Prod)&gt;0 Then\n&#039;Found a targeted product\nfFound = True\n\n&#039;Create the session object\nSet Session = oMsi.OpenProduct(Prod)\nSession.DoAction(&quot;CostInitialize&quot;)\nSession.DoAction(&quot;FileCost&quot;)\nSession.DoAction(&quot;CostFinalize&quot;)\nSet SessionDb = Session.Database\n\n&#039;Get a list of available tables\ndicTables.RemoveAll\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT `Name` FROM `_Tables` ORDER BY `Name`&quot;)\nqView.Execute\nDo\nSet Record = qView.Fetch\nIf Record Is Nothing then Exit Do\nIf Not dicTables.Exists(Record.StringData(1)) Then dicTables.Add Record.StringData(1),&quot;&quot;\nLoop\nqView.Close\nIf dicTables.Exists(&quot;File&quot;) Then\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT `File`,`Component_`,`FileName` FROM File&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicFtkComp.Exists(Record.StringData(1)) Then dicFtkComp.Add Record.StringData(1),Record.StringData(2)\nIf Not dicFtkFile.Exists(Record.StringData(1)) Then dicFtkFile.Add Record.StringData(1),Record.StringData(3)\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;File\n\nIf dicTables.Exists(&quot;Component&quot;) Then\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT `Component`,`ComponentId`,`Directory_` FROM Component&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicCompComp.Exists(Record.StringData(1)) Then dicCompComp.Add Record.StringData(1),Record.StringData(2)\nIf Not dicCompDir.Exists(Record.StringData(1)) Then dicCompDir.Add Record.StringData(1),Record.StringData(3)\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;Component\n\nIf dicTables.Exists(&quot;Directory&quot;) Then\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT DISTINCT `Directory` FROM Directory&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicDirectory.Exists(Record.Stringdata(1)) Then\nsTargetPath = &quot;&quot; : sTargetPath = Session.TargetPath(Record.Stringdata(1))\nIf NOT sTargetPath=&quot;&quot; Then dicDirectory.Add Record.Stringdata(1)&amp;&quot;_&quot;&amp;Prod,sTargetPath\nEnd If\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;Directory\n\nIf dicTables.Exists(&quot;MsiAssembly&quot;) Then\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT `Component_` FROM `MsiAssembly`&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicAssembly.Exists(Record.StringData(1)) Then dicAssembly.Add Record.StringData(1),&quot;&quot;\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;MsiAssembly\n\nIf dicTables.Exists(&quot;SxsMsmGenComponents&quot;) Then\nSet qView = Nothing\nSet qView = SessionDb.OpenView(&quot;SELECT `Component_` FROM SxsMsmGenComponents&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicAssembly.Exists(Record.StringData(1)) Then dicAssembly.Add Record.StringData(1),&quot;&quot;\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;SxsMsmGenComponents\n\nSet SessionDb = Nothing\nSet Session = Nothing\nEnd If &#039;Prod found\nNext &#039;Prod\n\nIf NOT fFound Then\nfDeepScan = False\nExit Sub\nEnd If\n\nXlSheet.Cells(HROW,F_PREDICTED).Value = &quot;Predicted Action&quot;\nXlSheet.Cells(HROW,F_COMPSTATE).Value = &quot;ComponentState&quot;\nXlSheet.Cells(HROW,F_CURSIZE).Value = &quot;FileSize On Disk&quot;\nXlSheet.Cells(HROW,F_CURVERSION).Value = &quot;Version On Disk&quot;\nXlSheet.Cells(HROW,F_CURHASH).Value = &quot;Hash On Disk&quot;\nXlSheet.Cells(HROW,F_FILEPATH).Value = &quot;FilePath&quot;\n\nFor Each Component in oMsi.Components\ndicComponents.Add Component,&quot;&quot;\nNext &#039;Component\n\n&#039;For Each Ftk in dicMspFile.Keys\nFor iRow = 2 To XlSheet.Columns(F_FILE).CurrentRegion.Rows.Count\nFtk = XlSheet.Cells(iRow,F_FILE).Value\nfCompLocal = False\nfKeyPathLow = False\nfPatchFileVersionLow = False\nfCopyPatchFile = False\n\nXlSheet.Cells(iRow,F_COMPSTATE).Value = &quot;Unknown Component&quot;\n\n&#039;Get ComponentID\nIf dicFtkComp.Exists(Ftk) Then\n&#039;This is a known file\n\nsComponent = &quot;&quot;\nsComponent = dicFtkComp.Item(Ftk)\nIf XlSheet.Cells(iRow,F_COMPONENT) = &quot;&quot; Then XlSheet.Cells(iRow,F_COMPONENT) = sComponent\n\n&#039;Since it&#039;s known it has the filenmae entry listed as well\nsFileName = &quot;&quot;\nsFileName = dicFtkFile.Item(Ftk)\nIf XlSheet.Cells(iRow,F_FILENAME) = &quot;&quot; Then XlSheet.Cells(iRow,F_FILENAME) = sFileName\n\nsComponentId = &quot;&quot;\nsComponentId = dicCompComp.Item(sComponent)\nIf dicComponents.Exists(sComponentId) Then\n\nsKeyPath = &quot;&quot;\nsComponentClients = &quot;&quot;\nFor Each CompClient in oMsi.ComponentClients(sComponentId)\nIf InStr(sPatchTargets,CompClient)&gt;0 Then\nsComponentClients = sComponentClients &amp;&quot;,&quot;&amp; CompClient\nIf oMsi.Product(CompClient,&quot;&quot;,4).ComponentState(sComponentId) = INSTALLSTATE_LOCAL Then\nfCompLocal = True\nsKeyPath = oMsi.ComponentPath(CompClient,sComponentId)\nProd = CompClient\nEnd If\nEnd If\nNext &#039;CompClient\nOn Error Goto 0\nIf fCompLocal Then XlSheet.Cells(iRow,F_COMPSTATE).Value = &quot;Local&quot; Else XlSheet.Cells(iRow,F_COMPSTATE).Value = &quot;Not Used&quot;\n\nsDirectory = &quot;&quot;\nsDirectory = dicDirectory.Item(dicCompDir.Item(sComponent)&amp;&quot;_&quot;&amp;Prod)\n\nsFullFileName = &quot;&quot;\nsLfn = &quot;&quot;\nIf InStr(sFileName,&quot;|&quot;)&gt;0 Then sLfn = Mid(sFileName,InStr(sFileName,&quot;|&quot;)+1) Else sLfn = sFileName\nIf dicAssembly.Exists(sComponent) Then\nsFullFileName=GetAssemblyPath(sLfn,sKeyPath,sDirectory)\nEnd If\nIf sFullFileName=&quot;&quot; Then sFullFileName = sDirectory &amp; sLfn\nXlSheet.Cells(iRow,F_FILEPATH).Value = sFullFileName\n\nsCurVer = &quot;&quot;\niCmp = 2\nIf fCompLocal Then\nfFileNotFound = NOT oFso.FileExists(sFullFileName)\nIf NOT fFileNotFound Then\nSet File = oFso.GetFile(sFullFileName)\nsCurVer = oFso.GetFileVersion(sFullFileName)\nIf NOT sCurVer = &quot;&quot; Then XlSheet.Cells(iRow,F_CURVERSION).Value = sCurVer Else XlSheet.Cells(iRow,F_CURHASH).Value = GetMsiFileHash(sFullFileName)\nXlSheet.Cells(iRow,F_CURSIZE).Value = File.Size\niCmp = CompareVersion(XlSheet.Cells(iRow,F_VERSION).Value,sCurVer,True)\nSelect Case iCmp\nCase -1\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Don&#039;t Update - Existing file has a newer version&quot;\nCase 0\nIf sCurVer = &quot;&quot; Then\n&#039;This is an unversioned file.\nfUserModified = False\nfUserModified = NOT(File.DateCreated = File.DateLastModified)\nIf fUserModified Then\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Don&#039;t Update - Existing file is unversioned but modified&quot;\nElse\nIf XlSheet.Cells(iRow,F_HASH).Value = XlSheet.Cells(iRow,F_CURHASH).Value Then\nIf XlSheet.Cells(iRow,F_CURHASH).Value = &quot;&quot; Then\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Update - Existing file is unversioned and unmodified - no source file hash provided to compare&quot;\nElse\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Don&#039;t Update - Hash matches existing file&quot;\nEnd If\nElse\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Update - Hash does not match existing file&quot;\nEnd If &#039;Hash\nEnd If &#039;UserModified\n\nElse\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Don&#039;t Update - Existing file is of an equal version&quot;\nEnd If\nCase 1\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Update - Existing file is of a lower version&quot;\nCase 2\nEnd Select\n&#039;Check on keypath version conflict\n&#039;If a versioned keypath does not have a higher version\n&#039;no files of the component will be considered for patching\nIf Not dicKeypathConflict.Exists(sComponent) Then \nIf LCase(sKeyPath) = LCase(sFullFileName) Then\nsVersionKeyPath = &quot;&quot;\nsVersionKeyPath = oFso.GetFileVersion(sKeyPath)\nIf NOT (CompareVersion(XlSheet.Cells(iRow,F_VERSION).Value,sVersionKeyPath,True)=1) Then\ndicKeypathConflict.Add sComponent,sVersionKeyPath\nEnd If\nEnd If\nEnd If\nElse\n&#039;The component is set to local but the file could not be found\n&#039;No prediction possible. Leave this field blank\nEnd If &#039;fFileNotFound\nElse\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Not Installed&quot;\nEnd If &#039;fCompLocal\n\nEnd If &#039;dicComponents.Exist\nElse\n&#039;Not a known file (added by patch or not part of installed SKU&#039;s)\nEnd If\nNext &#039;iRow\n\nIf dicKeypathConflict.Count &gt; 0 Then\nFor iRow = 2 To XlSheet.Columns(F_FILE).CurrentRegion.Rows.Count\nIf dicKeyPathConflict.Exists(XlSheet.Cells(iRow,F_COMPONENT).Value) Then\nIf XlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Update - Existing file is of a lower version&quot; Then _\nXlSheet.Cells(iRow,F_PREDICTED).Value = &quot;Don&#039;t Update - Existing KeyPath file has a newer version&quot;\nEnd If\nNext &#039;iRow\nEnd If &#039;dicKeypathConflict\n\nEnd Sub &#039;MspDeepScan\n&#039;=======================================================================================================\n\n&#039;Apply the _Transform view\n&#039;Create the File table view\n&#039;Add the data to the XlSheet\n\nSub FillTables(XlWkbk,XlSheet,MspDb,sMspFile,dicFiles,fOfficePatch,XmlDoc)\n\nDim Record,dicTransforms,dicKeys,tbl,Prod,MsiDb,Element,Elements,key\nDim dicLoadedTables\nDim qView\nDim sMst,sKey,sSqlCreateTable,sPatchTargets,sProduct,sProductTarget\nDim i,iRow,iMstCnt\nDim arrTables,arrColHeaders,arrPatchMst\n\niRow = 1\n&#039;File table columns\nXlSheet.Cells(iRow,F_FILE) = &quot;File&quot;\nXlSheet.Cells(iRow,F_COMPONENT) = &quot;Component_&quot;\nXlSheet.Cells(iRow,F_FILENAME) = &quot;FileName&quot;\nXlSheet.Cells(iRow,F_FILESIZE) = &quot;FileSize&quot;\nXlSheet.Cells(iRow,F_VERSION) = &quot;Version&quot;\nXlSheet.Cells(iRow,F_HASH) = &quot;Hash&quot;\nXlSheet.Cells(iRow,F_LANGUAGE) = &quot;Language&quot;\nXlSheet.Cells(iRow,F_ATTRIBUTE) = &quot;Attributes&quot;\nXlSheet.Cells(iRow,F_SEQUENCE) = &quot;Sequence&quot;\n\n&#039;Get the PatchTargets\nsPatchTargets = MspDb.SummaryInformation.Property(PID_TEMPLATE)\n\nSet dicLoadedTables = CreateObject(&quot;Scripting.Dictionary&quot;)\n&#039;The CustomAction table view has already been created for Office patches\n&#039;If fOfficePatch Then dicLoadedTables.Add &quot;CustomAction&quot;,&quot;CustomAction&quot;\n\n&#039;Try to dynamically obtain the schema from installed products\nFor Each Prod in oMsi.Products\nIf InStr(sPatchTargets,Prod)&gt;0 Then\nSet MsiDb = oMsi.OpenDatabase(oMsi.ProductInfo(Prod,&quot;LocalPackage&quot;),MSIOPENDATABASEMODE_READONLY)\narrTables = Split(GetDatabaseTables(MsiDb),&quot;,&quot;)\nFor Each tbl in arrTables\nIf NOT dicLoadedTables.Exists(tbl) Then\ndicLoadedTables.Add tbl,Prod\nsSqlCreateTable = &quot;CREATE TABLE `&quot; &amp; tbl &amp;&quot;` (&quot; &amp; GetTableColumnDef(MsiDb,tbl) &amp; &quot; PRIMARY KEY &quot; &amp; GetPrimaryTableKeys(MsiDb,tbl) &amp;&quot;)&quot;\nIf NOT dicSqlCreateTbl.Exists(tbl) Then dicSqlCreateTbl.Add tbl,sSqlCreateTable\nMspDb.OpenView(sSqlCreateTable).Execute\nIf tbl = &quot;File&quot; Then\nfNeedGenericSql = False\nEnd If\nEnd If\nNext &#039;tbl\nEnd If\nNext &#039;Prod\n\nIf fNeedGenericSql Then\nIf NOT MsiProvidedAsFile(MsiDb,MspDb,sMspFile,arrTables,dicLoadedTables) Then\nsProduct = &quot;&quot;\nsProduct = Right(Left(sPatchTargets,38),17)\nSelect Case sProduct\nCase &quot;CFE-0150048383C9}&quot;,&quot;CFE-0050048383C9}&quot;,&quot;60F-006097C998E7}&quot; &#039;O11,O10,O09\nsSqlCreateTable = &quot;CREATE TABLE `File` (`File` CHAR(72) NOT NULL, `Component_` CHAR(72) NOT NULL, `FileName` CHAR(255) NOT NULL LOCALIZABLE, `FileSize` LONG NOT NULL, `Version` CHAR(72), `Language` CHAR(20), `Attributes` SHORT, `Sequence` SHORT NOT NULL PRIMARY KEY `File`)&quot;\nCase Else\n&#039;Create the generic file table view\nsSqlCreateTable = SQL_CREATEFILETABLE\nEnd Select\nMspDb.OpenView(sSqlCreateTable).Execute\nElse\nfNeedGenericSql = False\nEnd If &#039;NOT MsiProvidedAsFile\nEnd If\n\n&#039;Get the patch embedded transforms\nSet dicTransforms = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet qView = MspDb.OpenView(&quot;SELECT `Name` FROM `_Storages` ORDER BY `Name`&quot;) : qView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\ndicTransforms.Add Record.StringData(1),Record.StringData(1)\nSet Record = qView.Fetch\nLoop\nqView.Close\n&#039;Apply the patch transforms to the patch itself\ndicKeys = dicTransforms.Keys\nOn Error Resume Next\nFor Each sMst in dicTransforms.Keys\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\n\nNext &#039;iMst\nOn Error Goto 0\n\n&#039;The file table is key thus handled in a dedicated loop\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\nSet qView = MspDb.OpenView(&quot;SELECT * FROM `_TransformView` WHERE `Table` = &#039;File&#039; ORDER BY `Row`&quot;)\nqView.Execute()\nSet Record = qView.Fetch\niRow = 2 : sKey = &quot;&quot;\n&#039;Get the first FTK\nIf NOT Record Is Nothing Then\nsKey = Record.StringData(3)\ndicFiles.Add sKey,&quot;&quot;\nEnd If\nXlSheet.Cells(iRow,F_FILE) = sKey\nDo Until Record Is Nothing\n&#039;Next FTK?\nIf NOT sKey = Record.StringData(3) Then \niRow = iRow + 1\nsKey = Record.StringData(3)\ndicFiles.Add sKey,&quot;&quot;\nXlSheet.Cells(iRow,F_FILE) = sKey\nEnd If\n&#039;Add data from _TransformView\nSelect Case Record.StringData(2)\nCase &quot;File&quot;\n&#039;XlSheet.Cells(iRow,F_FILE) = Record.StringData(4)\nCase &quot;FileSize&quot;\nXlSheet.Cells(iRow,F_FILESIZE) = Record.StringData(4)\nCase &quot;Component_&quot;\nXlSheet.Cells(iRow,F_COMPONENT) = Record.StringData(4)\nCase &quot;CREATE&quot;\nCase &quot;DELETE&quot;\nCase &quot;DROP&quot;\nCase &quot;FileName&quot;\nXlSheet.Cells(iRow,F_FILENAME) = Record.StringData(4)\ndicFiles.Item(sKey) = Record.StringData(4)\nCase &quot;Version&quot;\nXlSheet.Cells(iRow,F_VERSION) = Record.StringData(4)\nCase &quot;Language&quot;\nXlSheet.Cells(iRow,F_LANGUAGE) = Record.StringData(4)\nCase &quot;Attributes&quot;\nXlSheet.Cells(iRow,F_ATTRIBUTE) = Record.StringData(4)\nCase &quot;Sequence&quot;\nXlSheet.Cells(iRow,F_SEQUENCE) = Record.StringData(4)\nCase &quot;INSERT&quot;\nCase Else\nEnd Select\nSet Record = qView.Fetch\nLoop\n\n&#039;Only possible if the schema could be obtained\nIf fNeedGenericSql Then Exit Sub\n\n&#039;Handle all other tables per product\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\n\n&#039;drop all tables\n&#039;create all tables\n&#039;only apply the patch embedded transform pair that targets the product\n\n&#039;Handle .msi files provided in the patch folder\nIf fMsiProvidedAsFile Then\nDim f,File,Folder\n\nSet File = oFso.GetFile(sMspFile)\nSet Folder = oFso.GetFolder(File.ParentFolder)\n\n&#039;Check if we have a valid .msi file in the .msp folder location\nFor Each f in Folder.Files\nIf Right(LCase(f),4)=&quot;.msi&quot; Then\nProd = GetMsiProductCode(f.Path)\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\ni = 0\nFor Each Element in Elements\ni = i + 1\nIf Prod = Element.selectSingleNode(&quot;TargetProductCode&quot;).text Then\n&#039;Drop all tables\nOn Error Resume Next\nSet MsiDb = oMsi.OpenDatabase(f.Path,MSIOPENDATABASEMODE_READONLY)\narrTables = Split(GetDatabaseTables(MsiDb),&quot;,&quot;)\nFor Each tbl in arrTables\nMspDb.OpenView(&quot;DROP TABLE `&quot;&amp;tbl&amp;&quot;`&quot;).Execute\nNext &#039;tbl\n&#039;Recreate all tables\nFor Each tbl in arrTables\nMspDb.OpenView(dicSqlCreateTbl.Item(tbl)).Execute\nNext &#039;tbl\nOn Error Goto 0\niMstCnt = 0\nFor Each sMst in dicTransforms.Keys\niMstCnt = iMstCnt + 1\nIf (iMstCnt = i) Then\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nElseIf (iMstCnt = i + 1) Then \nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nExit For\nEnd If\nNext &#039;iMst\nFillTablesEx Prod,MsiDb,MspDb,XlWkbk,XlSheet,arrTables,arrColHeaders\nExit For\nEnd If &#039;Prod\nNext &#039;Element\nEnd If\nNext &#039;f\nEnd If &#039;fMsiProvidedAsFile\n\n&#039;Handle installed products\nFor Each Prod in oMsi.Products\nIf InStr(sPatchTargets,Prod)&gt;0 Then\n&#039;Drop all tables\nOn Error Resume Next\nSet MsiDb = oMsi.OpenDatabase(oMsi.ProductInfo(Prod,&quot;LocalPackage&quot;),MSIOPENDATABASEMODE_READONLY)\narrTables = Split(GetDatabaseTables(MsiDb),&quot;,&quot;)\nFor Each tbl in arrTables\nMspDb.OpenView(&quot;DROP TABLE `&quot;&amp;tbl&amp;&quot;`&quot;).Execute\nNext &#039;tbl\n&#039;Recreate all tables\nFor Each tbl in arrTables\nMspDb.OpenView(dicSqlCreateTbl.Item(tbl)).Execute\nNext &#039;tbl\nOn Error Goto 0\nsMst = Mid(dicProdMst.Item(Prod),2)\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nMspDb.ApplyTransform &quot;:#&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nFillTablesEx Prod,MsiDb,MspDb,XlWkbk,XlSheet,arrTables,arrColHeaders\nEnd If &#039;InStr\nNext &#039;Prod\n\nEnd Sub &#039;FillTables\n&#039;=======================================================================================================\n\nSub FillTablesEx (Prod,MsiDb,MspDb,XlWkbk,XlSheet,arrTables,arrColHeaders)\n\nDim tbl,Col,Record,sheet\nDim sKey,sPreFix\nDim iRow,iCol,i\nDim qView\nDim fExists\n\nSelect Case GetVersionMajor(Prod)\nCase 9, 10, 11\nsPreFix = Mid(Prod,4,2)\nCase 12, 14\nsPreFix = Mid(Prod,11,4)\nEnd Select\n\nFor Each tbl in arrTables\nIf NOT tbl = &quot;File&quot; Then\nSet arrColHeaders = Nothing\narrColHeaders = Split(GetTableColumnHeaders(MsiDb,tbl),&quot;,&quot;)\nIf IsArray(arrColHeaders) Then\nIf UBound(arrColHeaders)&gt;0 Then\n&#039;Create a new sheet\nFor Each sheet in XlWkbk.Worksheets\nIf Left(sPreFix&amp;&quot;_&quot;&amp;tbl,31) = sheet.name Then fExists = True\nNext &#039;sheet\n\nIf NOT fExists Then\nSet XlSheet = XlWkbk.Worksheets.Add\nXlSheet.Name = Left(sPreFix&amp;&quot;_&quot;&amp;tbl,31)\nXlSheet.Move ,XlWkbk.Sheets(XlWkbk.Sheets.Count)\niRow = 1 : iCol = 1 : i = 0\n\n&#039;Fill the header row\nFor Each Col in arrColHeaders\nXlSheet.Cells(iRow,i+1) = arrColHeaders(i)\ni=i+1\nNext &#039;Col\n\n&#039;Initiate the view\nSet qView = MspDb.OpenView(&quot;SELECT * FROM `_TransformView` WHERE `Table` = &#039;&quot;&amp;tbl&amp;&quot;&#039; ORDER BY `Row`&quot;)\nqView.Execute()\nSet Record = qView.Fetch\ni = 0 : iRow = 2 : sKey = &quot;&quot;\n&#039;Get the first Row\nIf NOT Record Is Nothing Then\nsKey = Record.StringData(3)\nXlSheet.Cells(iRow,1) = Record.StringData(3)\nElse\nXlSheet.Delete\nEnd If\n&#039;XlSheet.Cells(iRow,F_FILE) = sKey\nDo Until Record Is Nothing\n&#039;Next Row?\nIf NOT sKey = Record.StringData(3) Then \niRow = iRow + 1\nsKey = Record.StringData(3)\nXlSheet.Cells(iRow,1) = Record.StringData(3)\nEnd If\n&#039;Add data from _TransformView\niCol = 0\nFor Each Col in arrColHeaders\niCol=iCol+1\nIf Record.StringData(2) = Col Then\nXlSheet.Cells(iRow,iCol) = Record.StringData(4)\nExit For\nEnd If\nNext &#039;Col\nSet Record = qView.Fetch\nLoop\nEnd If &#039;fExists\nEnd If &#039;UBound\nEnd If &#039;IsArray\nEnd If\nNext &#039;tbl\n\nEnd Sub &#039;FillTablesEx\n&#039;=======================================================================================================\n\nFunction MsiProvidedAsFile(MsiDb,MspDb,sMspFile,arrTables,dicLoadedTables)\n\nDim f,File,Folder,Prod,tbl\nDim sSqlCreateTable,sPatchTargets\n\nMsiProvidedAsFile = False\n\nSet File = oFso.GetFile(sMspFile)\nSet Folder = oFso.GetFolder(File.ParentFolder)\n\n&#039;Check if we have a valid .msi file in the .msp folder location\nFor Each f in Folder.Files\nIf Right(LCase(f),4)=&quot;.msi&quot; Then\nProd = GetMsiProductCode(f.Path)\nsPatchTargets = MspDb.SummaryInformation.Property(PID_TEMPLATE)\nIf InStr(sPatchTargets,Prod)&gt;0 Then\nMsiProvidedAsFile = True\nfMsiProvidedAsFile = True\nsExternalMsi = f.Path\nSet MsiDb = oMsi.OpenDatabase(f.Path,MSIOPENDATABASEMODE_READONLY)\narrTables = Split(GetDatabaseTables(MsiDb),&quot;,&quot;)\nFor Each tbl in arrTables\nIf NOT dicLoadedTables.Exists(tbl) Then\ndicLoadedTables.Add tbl,Prod\nsSqlCreateTable = &quot;CREATE TABLE `&quot; &amp; tbl &amp;&quot;` (&quot; &amp; GetTableColumnDef(MsiDb,tbl) &amp; &quot; PRIMARY KEY &quot; &amp; GetPrimaryTableKeys(MsiDb,tbl) &amp;&quot;)&quot;\nIf NOT dicSqlCreateTbl.Exists(tbl) Then dicSqlCreateTbl.Add tbl,sSqlCreateTable\nMspDb.OpenView(sSqlCreateTable).Execute\nEnd If\nNext &#039;tbl\nEnd If &#039;Prod\nEnd If &#039;.msi\nNext &#039;f\n\nEnd Function &#039;MsiProvidedAsFile\n&#039;=======================================================================================================\n\nSub AddTransformProductName (dicTransformRow,MspDb)\n\nEnd Sub &#039;AddTransformProductName\n&#039;=======================================================================================================\n\nSub ExtendTransformTable (XlSheet,dicTransformRow,MspDb)\n\nDim Record,dicTransforms,dicKeys\nDim qView\nDim sMst,sKey,sSource,sTarget,sSqlCreateTable\nDim iRow,iCol\nDim fAdd\n\nXlSheet.Cells(1,3).Value = &quot;REINSTALLMODE&quot;\nXlSheet.Cells(1,4).Value = &quot;REINSTALL&quot;\nXlSheet.Cells(1,5).Value = &quot;PATCHNEWSUMMARYSUBJECT&quot;\nXlSheet.Cells(1,6).Value = &quot;PATCHNEWSUMMARYCOMMENTS&quot;\n\n&#039;Get the patch embedded transforms\nSet dicTransforms = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet qView = MspDb.OpenView(&quot;SELECT `Name` FROM `_Storages` ORDER BY `Name`&quot;) : qView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\ndicTransforms.Add Record.StringData(1),Record.StringData(1)\nSet Record = qView.Fetch\nLoop\nqView.Close\n&#039;Apply the patch transforms to the patch itself\ndicKeys = dicTransforms.Keys\n&#039;Prepare CustomAction table query\nsSqlCreateTable = SQL_CREATECATABLE\nIf dicSqlCreateTbl.Exists(&quot;CustomAction&quot;) Then sSqlCreateTable = dicSqlCreateTbl.Item(&quot;CustomAction&quot;)\n\nFor Each sMst in dicTransforms.Keys\n&#039;Only care about non pound transforms\nIf NOT InStr(sMst,&quot;#&quot;)&gt;0 Then\n\n&#039;Create the CustomAction table view\nMspDb.OpenView(sSqlCreateTable).Execute\n\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nMspDb.ApplyTransform &quot;:#&quot; &amp; sMst,MSITRANSFORMERROR_ALL\n\niRow = dicTransformRow.Item(&quot;:&quot;&amp;sMst)\nsKey = &quot;&quot; : sSource = &quot;&quot; : sTarget = &quot;&quot;\nfAdd = False\nSet qView = MspDb.OpenView(&quot;SELECT * FROM `_TransformView` WHERE `Table` = &#039;CustomAction&#039; ORDER BY `Row`&quot;)\nqView.Execute()\nSet Record = qView.Fetch\n&#039;Get the first key\nIf NOT Record Is Nothing Then\nsKey = Record.StringData(3)\nEnd If\nDo Until Record Is Nothing\n&#039;Next key\nIf NOT sKey = Record.StringData(3) Then \nIf fAdd Then\nIf XlSheet.Cells(iRow,iCol).Value = &quot;&quot; Then XlSheet.Cells(iRow,iCol).Value = sTarget\nEnd If\nsKey = Record.StringData(3)\nsSource = &quot;&quot; : sTarget = &quot;&quot;\nfAdd = False\nEnd If\n&#039;Add data from _TransformView\nSelect Case Record.StringData(2)\nCase &quot;Action&quot;\nCase &quot;Type&quot;\nCase &quot;Source&quot;\nsSource = Record.StringData(4)\nIf sSource = &quot;REINSTALLMODE&quot; Then fAdd = True\nIf sSource = &quot;REINSTALL&quot; Then fAdd = True\nIf fAdd AND sSource = &quot;REINSTALL&quot; Then iCol=4 Else iCol=3\nCase &quot;Target&quot;\nsTarget = Record.StringData(4)\nCase Else\nEnd Select\nSet Record = qView.Fetch\nLoop\nIf fAdd Then\nIf XlSheet.Cells(iRow,iCol).Value = &quot;&quot; Then XlSheet.Cells(iRow,iCol).Value = sTarget\nEnd If\nMspDb.OpenView(&quot;DROP TABLE `CustomAction`&quot;).Execute\nEnd If &#039;non pound\nNext &#039;sMst\n\nFor Each sMst in dicTransforms.Keys\n&#039;Only care about pound transforms\nIf InStr(sMst,&quot;#&quot;)&gt;0 Then\n\n&#039;Create the Propery table view\nsSqlCreateTable = SQL_CREATEPROPTABLE\nIf dicSqlCreateTbl.Exists(&quot;Property&quot;) Then sSqlCreateTable = dicSqlCreateTbl.Item(&quot;Property&quot;)\nMspDb.OpenView(sSqlCreateTable).Execute\n\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\n\niRow = dicTransformRow.Item(&quot;:&quot;&amp;sMst)\nsKey = &quot;&quot; : sSource = &quot;&quot; : sTarget = &quot;&quot;\nfAdd = False\nSet qView = MspDb.OpenView(&quot;SELECT * FROM `_TransformView` WHERE `Table` = &#039;Property&#039; ORDER BY `Row`&quot;)\nqView.Execute()\nSet Record = qView.Fetch\n&#039;Get the first key\nIf NOT Record Is Nothing Then\nsKey = Record.StringData(3)\nEnd If\nDo Until Record Is Nothing\n&#039;Next key\nIf NOT sKey = Record.StringData(3) Then \nIf fAdd Then\nIf XlSheet.Cells(iRow,iCol).Value = &quot;&quot; Then XlSheet.Cells(iRow,iCol).Value = sTarget\nEnd If\nsKey = Record.StringData(3)\nsSource = &quot;&quot; : sTarget = &quot;&quot;\nfAdd = False\nEnd If\n&#039;Add data from _TransformView\nSelect Case Record.StringData(2)\nCase &quot;Value&quot;\nIf sKey = &quot;PATCHNEWSUMMARYSUBJECT&quot; Then fAdd = True\nIf sKey = &quot;PATCHNEWSUMMARYCOMMENTS&quot; Then fAdd = True\nIf fAdd AND sKey = &quot;PATCHNEWSUMMARYCOMMENTS&quot; Then iCol=6 Else iCol=5\nIf fAdd Then sTarget = Record.StringData(4)\nCase Else\nEnd Select\nIf fAdd Then\nIf XlSheet.Cells(iRow,iCol).Value = &quot;&quot; Then XlSheet.Cells(iRow,iCol).Value = sTarget\nfAdd = False\nEnd If\nSet Record = qView.Fetch\nLoop\nMspDb.OpenView(&quot;DROP TABLE `Property`&quot;).Execute\nEnd If\nNext &#039;sMst\n\nEnd Sub &#039;ExtendTransformTable\n&#039;=======================================================================================================\n\nSub AddMspHashes (XlWkbk)\nDim Sheet,XlSheet\nDim dicFileRow\nDim sFile\nDim iF_Row,iRow\n\nFor Each Sheet in XlWkbk.Sheets\nIf Sheet.Name = &quot;MsiFileHash_Table&quot; Then\n&#039;Buid a file dic\nSet dicFileRow = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet XlSheet = XlWkbk.Sheets(&quot;File_Table&quot;)\nFor iF_Row = 2 To XlSheet.Columns(F_FILE).CurrentRegion.Rows.Count\ndicFileRow.Add XlSheet.Cells(iF_Row,F_FILE).Value,iF_Row\nNext &#039;iF_Row\n\n&#039;Add the file hashes\nFor iRow = 2 To Sheet.Columns(F_FILE).CurrentRegion.Rows.Count\nsFile = &quot;&quot;\nsFile = Sheet.Cells(iRow,F_FILE).Value\nIf dicFileRow.Exists(sFile) Then\niF_Row = dicFileRow.Item(sFile)\nXlSheet.Cells(iF_Row,F_HASH).Value = Sheet.Cells(iRow,3).Value&amp;&quot;,&quot;&amp;Sheet.Cells(iRow,4).Value&amp;&quot;,&quot;&amp;Sheet.Cells(iRow,5).Value&amp;&quot;,&quot;&amp;Sheet.Cells(iRow,6).Value\nEnd If\nNext &#039;iRow\nEnd If\nNext &#039;XlSheet\nEnd Sub &#039;AddMspHashes\n&#039;=======================================================================================================\n\nSub AddFileNames (XlWkbk)\nDim XlSheet,MsiDb,Record\nDim dicFtkSize,dicTables,dicFtkFile\nDim qView\nDim sFileName,sFtk\nDim iRow\n\nSet dicTables = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicFtkFile = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet dicFtkSize = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet MsiDb = oMsi.OpenDatabase(sExternalMsi,MSIOPENDATABASEMODE_READONLY)\n\n&#039;Get a list of available tables\nSet qView = Nothing\nSet qView = MsiDb.OpenView(&quot;SELECT `Name` FROM `_Tables` ORDER BY `Name`&quot;)\nqView.Execute\nDo\nSet Record = qView.Fetch\nIf Record Is Nothing then Exit Do\nIf Not dicTables.Exists(Record.StringData(1)) Then dicTables.Add Record.StringData(1),&quot;&quot;\nLoop\nqView.Close\nIf dicTables.Exists(&quot;File&quot;) Then\nSet qView = Nothing\nSet qView = MsiDb.OpenView(&quot;SELECT `File`,`FileSize`,`FileName` FROM File&quot;)\nqView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\nIf Not dicFtkSize.Exists(Record.StringData(1)) Then dicFtkSize.Add Record.StringData(1),Record.StringData(2)\nIf Not dicFtkFile.Exists(Record.StringData(1)) Then dicFtkFile.Add Record.StringData(1),Record.StringData(3)\nSet Record = qView.Fetch\nLoop\nqView.Close\nEnd If &#039;File\n\nFor Each XlSheet in XlWkbk.Sheets\nIf XlSheet.Name = &quot;File_Table&quot; Then\nFor iRow = 2 To XlSheet.Columns(F_FILE).CurrentRegion.Rows.Count\nsFtk = XlSheet.Cells(iRow,F_FILE).Value\nIf dicFtkFile.Exists(sFtk) Then\nsFileName = &quot;&quot;\nsFileName = dicFtkFile.Item(sFtk)\nIf XlSheet.Cells(iRow,F_FILENAME) = &quot;&quot; Then XlSheet.Cells(iRow,F_FILENAME) = sFileName\nEnd If\nIf dicFtkSize.Exists(sFtk) Then\nsFileName = &quot;&quot;\nsFileName = dicFtkSize.Item(sFtk)\nIf XlSheet.Cells(iRow,F_FILESIZE) = &quot;&quot; Then XlSheet.Cells(iRow,F_FILESIZE) = sFileName\nEnd If\nNext\nEnd If\nNext &#039;XlSheet\nEnd Sub &#039;AddFileNames\n&#039;=======================================================================================================\n\n&#039;Windows Installer Helper Routines\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\n\n&#039;=======================================================================================================\n\n&#039;Scans for a dynamic (optimized) SUPdateLocation folder structure\n&#039;Builds a global dictionary with identified folders\nSub DiscoverDynSUpdateFolders\nDim Product\nDim sRelPath, sCulture\n\nSet dicDynCultFolders = CreateObject(&quot;Scripting.Dictionary&quot;)\nFor Each Product in oMsi.Products\nIf Len(Product) = 38 Then\nIf IsOfficeProduct(Product) Then\nsRelPath = &quot;&quot;\nsRelPath = GetVersionMajor(Product) &amp; &quot;.0&quot;\nSelect Case sRelPath\nCase &quot;9.0&quot;,&quot;10.0&quot;,&quot;11.0&quot;\nsCulture = LCase(GetCultureInfo(CInt(&quot;&amp;h&quot; &amp; Mid(Product,6,4))))\nCase &quot;12.0&quot;,&quot;14.0&quot;\nsCulture = LCase(GetCultureInfo(CInt(&quot;&amp;h&quot; &amp; Mid(Product,16,4))))\nIf Mid(Product,11,1) = &quot;1&quot; Then sRelPath = sRelPath &amp; &quot;\\Server&quot; Else sRelPath = sRelPath &amp; &quot;\\Client&quot;\nIf Mid(Product,21,1) = &quot;1&quot; Then sRelPath = sRelPath &amp; &quot;\\x64&quot; Else sRelPath = sRelPath &amp; &quot;\\x86&quot;\nCase Else\nEnd Select\nIf sCulture = &quot;neutral&quot; Then sCulture = &quot;x-none&quot;\nsRelPath = sRelPath &amp; &quot;\\&quot; &amp; sCulture\nIf NOT sCulture = &quot;&quot; Then\nIf NOT dicDynCultFolders.Exists(sCulture) Then dicDynCultFolders.Add sCulture,sCulture\nEnd If\nEnd If &#039;IsOfficeProduct\nEnd If &#039;38\nNext &#039;Product\n\nfDynSUpdateDiscovered = True\nEnd Sub &#039;DiscoverSUpdateFolders\n&#039;=======================================================================================================\n\n&#039;Returns a boolean to determine if Excel is installed on the computer\nFunction XLInstalled()\nDim Product\n\nXLInstalled = False\nFor Each Product In oMsi.Products\nIf oMsi.FeatureState(Product,&quot;EXCELFiles&quot;) = MSIINSTALLSTATE_LOCAL Then\nXLInstalled = True : Exit For\nEnd If\nNext &#039;Product\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Returns the Msi file hash values as comma separated string list\nFunction GetMsiFileHash(sFullFileName)\nDim Record\n\nOn Error Resume Next\n\nGetMsiFileHash = &quot;&quot;\nSet Record = oMsi.FileHash(sFullFileName,0)\nGetMsiFileHash = Record.StringData(1)&amp;&quot;,&quot;&amp;Record.StringData(2)&amp;&quot;,&quot;&amp;Record.StringData(3)&amp;&quot;,&quot;&amp;Record.StringData(4)\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Obtain the ProductCode (GUID) from a .msi package\n&#039;The function will open the .msi database and query the &#039;Property&#039; table to retrieve the ProductCode\nFunction GetMsiProductCode(sMsiFile)\n\nDim MsiDb,Record\nDim qView\n\nOn Error Resume Next\n\nGetMsiProductCode = &quot;&quot;\nSet Record = Nothing\n\nSet MsiDb = oMsi.OpenDatabase(sMsiFile,MSIOPENDATABASEMODE_READONLY)\nSet qView = MsiDb.OpenView(&quot;SELECT `Value` FROM Property WHERE `Property` = &#039;ProductCode&#039;&quot;)\nqView.Execute\nSet Record = qView.Fetch\nGetMsiProductCode = Record.StringData(1)\nqView.Close\nEnd Function &#039;GetMsiProductCode\n&#039;=======================================================================================================\n\n&#039;Obtain the ProductVersion from a .msi package\n&#039;The function will open the .msi database and query the &#039;Property&#039; table to retrieve the ProductCode\nFunction GetMsiProductVersion(sMsiFile)\n\nDim MsiDb,Record\nDim qView\n\nOn Error Resume Next\n\nGetMsiProductVersion = &quot;&quot;\nSet Record = Nothing\n\nSet MsiDb = oMsi.OpenDatabase(sMsiFile,MSIOPENDATABASEMODE_READONLY)\nSet qView = MsiDb.OpenView(&quot;SELECT `Value` FROM Property WHERE `Property` = &#039;ProductVersion&#039;&quot;)\nqView.Execute\nSet Record = qView.Fetch\nIf NOT Record Is Nothing Then GetMsiProductVersion = Record.StringData(1)\nqView.Close\nEnd Function &#039;GetMsiProductVersion\n&#039;=======================================================================================================\n\n&#039;Obtain the PackageCode (GUID) from a .msi package\n&#039;The function will the .msi&#039;S SummaryInformation stream\nFunction GetMsiPackageCode(sMsiFile)\n\nOn Error Resume Next\n\nGetMsiPackageCode = &quot;&quot;\nGetMsiPackageCode = oMsi.SummaryInformation(sMsiFile,MSIOPENDATABASEMODE_READONLY).Property(PID_REVNUMBER)\n\nEnd Function &#039;GetMsiPackageCode\n&#039;=======================================================================================================\n\n&#039;Returns a string with the patch sequence data\nFunction GetLegacyMspSeq(Msp)\n\nDim i\nDim sSeq\nDim arrTitle\n\nsSeq = &quot;&quot;\narrTitle = Split(Msp.SummaryInformation.Property(PID_TITLE),&quot;;&quot;)\nIf IsArray(arrTitle) Then\nIf UBound(arrTitle)&gt;1 Then\nsSeq = arrTitle(2)\nFor i = 1 To Len(sSeq)\nIf NOT (Asc(Mid(sSeq,i,1)) &gt;= 48 AND Asc(Mid(sSeq,i,1)) &lt;= 57) Then \nsSeq = &quot;&quot;\nExit For\nEnd If\nNext &#039;i\nEnd If\nEnd If\n\nGetLegacyMspSeq = sSeq\n\nEnd Function &#039;GetMspSequence\n&#039;=======================================================================================================\n\n&#039;Detect the real product build number based on .msi and .msp build information\n&#039;to allow verification of the registered build number\nFunction GetRealBuildVersion(sAppliedPatches,sProductCode)\n\nDim Element,Elements\n\nDim sProductVersionReg,sProductVersionMsi\n\nDim iIndex\n\nOn Error Resume Next\n\nsProductVersionReg = oMsi.ProductInfo(sProductCode,&quot;VersionString&quot;)\nsProductVersionMsi = GetMsiProductVersion(oMsi.ProductInfo(sProductCode,&quot;LocalPackage&quot;))\nsProductVersionReal = sProductVersionMsi\n\nFor iIndex = 0 To UBound(arrSUpdatesAll)\nIf (InStr(arrSUpdatesAll(iIndex,COL_TARGETS),sProductCode)&gt;0) Then\nIf InStr(sAppliedPatches,arrSUpdatesAll(iIndex,COL_PATCHCODE))&gt;0 Then\nIf IsMinorUpdate(sProductCode,arrSUpdatesAll(iIndex,COL_PATCHXML)) Then\nXmlDoc.LoadXml(arrSUpdatesAll(iIndex,COL_PATCHXML))\nSet Elements = XmlDoc.GetElementsByTagName(&quot;TargetProduct&quot;)\nFor Each Element in Elements\nIf Element.selectSingleNode(&quot;TargetProductCode&quot;).text = sProductCode Then\nIf Element.selectSingleNode(&quot;UpdatedVersion&quot;).text &gt; sProductVersionReal Then _\nsProductVersionReal = Element.selectSingleNode(&quot;UpdatedVersion&quot;).text\nEnd If\nNext &#039;Element\nEnd If\nEnd If\nEnd If &#039;InStr(arrSUpdatesAll\u2026\nNext &#039;iIndex\n\nIf NOT Err=0 Then GetRealBuildVersion=sProductVersionReg Else GetRealBuildVersion=sProductVersionReal\n\nEnd Function &#039;GetRealBuildVersion\n&#039;=======================================================================================================\n\n&#039;Checks if a ProductCode belongs to an Office family\nFunction IsOfficeProduct(sProductCode)\nOn Error Resume Next\n\nIsOfficeProduct = False\nIf InStr(OFFICE_ALL, UCase(Right(sProductCode,28))) &gt; 0 OR _\nInStr(OFFICEID, UCase(Right(sProductCode,17))) &gt; 0 Then\nIf Not Err = 0 Then Exit Function\nIsOfficeProduct = True\nEnd If\n\nEnd Function &#039;IsOfficeProduct\n&#039;=======================================================================================================\n\n&#039;Checks if a PatchCode belongs to an Office family\nFunction IsOfficePatch(sPatchTargets)\n\nDim arrPatchTargets\n\nDim Target\n\nOn Error Resume Next\n\nIsOfficePatch = False\nIf NOT Len(sPatchTargets)&gt;1 Then Exit Function\n\narrPatchTargets = Split(sPatchTargets,&quot;;&quot;)\nFor Each Target in arrPatchTargets\nIf InStr(OFFICE_ALL, UCase(Right(Target,28))) &gt; 0 OR _\nInStr(OFFICEID, UCase(Right(Target,17))) &gt; 0 Then\nIf Not Err = 0 Then Exit Function\nIsOfficePatch = True\nExit Function\nEnd If\nNext &#039;Target\n\nEnd Function &#039;IsOfficePatch\n&#039;==============================================================================================\n\n&#039;Verify Windows Installer metadata are in a healthy state and initiate fixup if needed\nSub EnsurePatchMetadata (Patch,sProductUserSid)\nOn Error Resume Next\n\nDim RegItem,Folder,File,RegItems,MspDb,Record\nDim dicTransforms\ndim qView\nDim fGlobalConfigPatchExists,fGlobalConfigProductExists,fConfigPatchExists\nDim fConfigProductExists,fLocalPatchPackageDataExist,fNoError\nDim sPatchCodeCompressed,sRegUserSID,sHive,sRegHive,sRegKey,sRegName,sTmp,sProductCode\nDim sProductCodeCompressed,sItem,sLocalMSP,sSqlCreateTable,sMst,sDiskPrompt,sVolumeLabel\nDim sDiskId,sKey,sMspClasses,sPackageName\nDim iContext\n\nfLocalPatchPackageDataExist = False\nfGlobalConfigPatchExists = False\nfGlobalConfigProductExists = False\nfConfigPatchExists = False\nfConfigProductExists = False\nfNoError = True\n\nsPatchCodeCompressed = GetCompressedGuid(Patch.PatchCode)\nsProductCode = Patch.ProductCode\nsProductCodeCompressed = GetCompressedGuid(sProductCode)\n\nErr.Clear\nsLocalMSP = &quot;&quot;\nsLocalMSP = Patch.Patchproperty(&quot;LocalPackage&quot;)\nSelect Case (Err.number)\nCase 0\nfLocalPatchPackageDataExist = True\n\nCase -2147023249\n&#039;MSI API Error -&gt; Failed to get value for local package\nfNoError = False\nErr.Clear\nCase Else\n&#039;Unexpected Error\nfNoError = False\nErr.Clear\nEnd Select\n\n&#039;Prepare UserSID variable needed for registry operations\nsRegUserSID = &quot;S-1-5-18\\&quot;\nIf NOT sProductUserSid = &quot;&quot; Then sRegUserSID = sProductUserSid &amp; &quot;\\&quot;\n\n&#039;Check Global Config location\n&#039;============================\nsHive = HKLM\nsRegHive = &quot;HKEY_LOCAL_MACHINE\\&quot;\n\n&#039;Check local package registration\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\n&#039;REG_GLOBALCONFIG = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\&quot;\nsRegKey = REG_GLOBALCONFIG &amp; sRegUserSID &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nsRegName = &quot;LocalPackage&quot;\nfGlobalConfigPatchExists = RegValExists(sHive,sRegKey,sRegName)\nIf Not fGlobalConfigPatchExists Then \nsTmp = vbTab &amp; &quot;Missing patch metadata. Failed to read value from: &quot; &amp; sRegHive &amp; sRegKey &amp; sRegName\nLog sTmp\nLogSummary sProductCode,sTmp\nfNoError = False\nEnd If\n\n&#039;Check if patchkey exists for the product\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\n&#039;REG_GLOBALCONFIG = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\&quot;\nsRegKey = REG_GLOBALCONFIG &amp; sRegUserSID &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\nfGlobalConfigProductExists = RegKeyExists (sHive, sRegKey &amp; sPatchCodeCompressed)\nIf Not fGlobalConfigProductExists Then \nsTmp = vbTab &amp; &quot;Missing patch metadata. Failed to locate key: &quot; &amp; sRegHive &amp; sRegKey &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nLog sTmp\nLogSummary sProductCode,sTmp\n&#039;This could be related to a Windows Installer Upgrade scenario\nEnd If\n\n&#039;Check per-user\/per-machine\/managed location\n&#039;===========================================\nSelect Case (Patch.Context)\nCase 1\n&#039;Context = &quot;USERMANAGED&quot;\nsHive = HKLM\nsRegHive = &quot;HKEY_LOCAL_MACHINE\\&quot;\n&#039;REG_PRODUCTPERUSERMANAGED = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\Managed\\&quot;\nsRegKey = REG_PRODUCTPERUSERMANAGED &amp;sRegUserSID &amp; &quot;\\Installer\\Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nCase 2\n&#039;Context = &quot;USER UNMANAGED&quot;\nsHive = HKU\nsRegHive = &quot;HKEY_USERS\\&quot;\n&#039;REG_PRODUCT = &quot;Software\\Classes\\Installer\\&quot;\nsRegKey = sRegUserSID &amp; REG_PRODUCT &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nCase Else &#039; = Case 4\n&#039;Context = &quot;MANAGED&quot;\nsHive = HKLM\nsRegHive = &quot;HKEY_LOCAL_MACHINE\\&quot;\n&#039;REG_PRODUCT = &quot;Software\\Classes\\Installer\\&quot;\nsRegKey = REG_PRODUCT &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nEnd Select\nsMspClasses = sRegKey\n\n&#039;Check registration in &#039;Patches&#039; section\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\nfConfigPatchExists = RegKeyExists(sHive, sRegKey &amp; &quot;SourceList&quot;)\nIf Not fConfigPatchExists Then \nsTmp = vbTab &amp; &quot;Missing patch metadata. Failed to locate key: &quot; &amp; sRegHive &amp; sRegKey &amp; &quot;SourceList\\&quot;\nLog sTmp\nLogSummary sProductCode,sTmp\nfNoError = False\nEnd If\n\n&#039;Check if patchkey exists for the product\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014-\nSelect Case (Patch.Context)\nCase 1\n&#039;Context = &quot;USERMANAGED&quot;\n&#039;REG_PRODUCTPERUSERMANAGED = &quot;Software\\Microsoft\\Windows\\CurrentVersion\\Installer\\Managed\\&quot;\nsRegKey = REG_PRODUCTPERUSERMANAGED &amp; sRegUserSID &amp; &quot;\\Installer\\Products\\&quot;&amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\nCase 2\n&#039;Context = &quot;USER UNMANAGED&quot;\n&#039;REG_PRODUCTPERUSER = &quot;Software\\Microsoft\\Installer\\&quot;\nsRegKey = sRegUserSID &amp; REG_PRODUCTPERUSER &amp; &quot;Products\\&quot;&amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\nCase Else &#039; = Case 4\n&#039;Context = &quot;MANAGED&quot;\n&#039;REG_PRODUCT = &quot;Software\\Classes\\Installer\\&quot;\nsRegKey = REG_PRODUCT &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\nEnd Select\nsRegName = sPatchCodeCompressed\nfConfigProductExists = RegValExists(sHive,sRegKey,sRegName)\nIf Not fConfigProductExists Then \nsTmp = vbTab &amp; &quot;Missing patch metadata. Failed to read value from: &quot; &amp; sRegHive &amp; sRegKey &amp; sRegName\nLog sTmp\nEnd If\n\nsRegName = &quot;Patches&quot;\nsTmp = &quot;&quot;\nRegItems = oWShell.RegRead(sRegHive &amp; sRegKey &amp; sRegName)\nFor Each sItem In RegItems\nsTmp = sTmp &amp; sItem\nNext\n\nfConfigProductExists = (InStr(sTmp,sPatchCodeCompressed) &gt; 0)\n\nIf NOT fNoError AND NOT fDetectOnly Then &#039;FixMspReg Patch\nsTmp = vbTab&amp;&quot;Repair: &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Fixing patch registration.&quot;\nIf fDetectOnly Then sTmp = vbTab&amp;&quot;Error: Registration broken for &#039;&quot;&amp;Patch.PatchCode&amp;&quot;&#039;. Patch registration would be fixed.&quot;\nLog sTmp\nLogSummary Patch.PatchCode,sTmp\n\nIf NOT fLocalPatchPackageDataExist Then FixMspGlobalReg Patch.PatchCode\nIf NOT fConfigPatchExists Then\nSet MspDb = oMsi.OpenDatabase(sLocalMSP,MSIOPENDATABASEMODE_PATCHFILE)\nsSqlCreateTable = &quot;CREATE TABLE `Media` (`DiskId` SHORT NOT NULL, `LastSequence` LONG NOT NULL, `DiskPrompt` CHAR(64) LOCALIZABLE, `Cabinet` CHAR(255), `VolumeLabel` CHAR(32), `Source` CHAR(72) PRIMARY KEY `DiskId`)&quot;\nMspDb.OpenView(sSqlCreateTable).Execute\n\n&#039;Get the patch embedded transforms\nSet dicTransforms = CreateObject(&quot;Scripting.Dictionary&quot;)\nSet qView = MspDb.OpenView(&quot;SELECT `Name` FROM `_Storages` ORDER BY `Name`&quot;) : qView.Execute\nSet Record = qView.Fetch\nDo Until Record Is Nothing\ndicTransforms.Add Record.StringData(1),Record.StringData(1)\nSet Record = qView.Fetch\nLoop\nqView.Close\n&#039;Apply the patch transforms to the patch itself\nFor Each sMst in dicTransforms.Keys\nMspDb.ApplyTransform &quot;:&quot; &amp; sMst,MSITRANSFORMERROR_ALL\nSet TestSumInfo = MspDb.SummaryInformation\nNext &#039;sMst\n\n&#039;Obtain the DiskPrompt and VolumeLabel\nSet qView = MspDb.OpenView(&quot;SELECT * FROM `_TransformView` WHERE `Table` = &#039;Media&#039; ORDER BY `Row`&quot;)\nqView.Execute()\nSet Record = qView.Fetch\nIf NOT Record Is Nothing Then\nsKey = Record.StringData(3)\nEnd If\nDo Until Record Is Nothing\n&#039;Next FTK?\nIf NOT sKey = Record.StringData(3) Then Exit Do\n&#039;Add data from _TransformView\nSelect Case Record.StringData(2)\nCase &quot;DiskId&quot;\nsDiskId = Record.StringData(4)\nCase &quot;DiskPrompt&quot;\nsDiskPrompt = Record.StringData(4)\nCase &quot;VolumeLabel&quot;\nsVolumeLabel = Record.StringData(4)\nCase &quot;CREATE&quot;\nCase &quot;DELETE&quot;\nCase &quot;DROP&quot;\nCase &quot;INSERT&quot;\nCase Else\nEnd Select\nSet Record = qView.Fetch\nLoop\nqView.Close\n&#039;StdPackageName\nSet qView = MspDb.OpenView(&quot;SELECT `Property`,`Value` FROM MsiPatchMetadata WHERE `Property`=&#039;StdPackageName&#039;&quot;)\nqView.Execute : Set Record = qView.Fetch()\nIf Not Record Is Nothing Then\nsPackageName = Record.StringData(2)\nElse\nsPackageName = &quot;&quot;\nEnd If\nqView.Close\nPatch.SourceListAddSource MSISOURCETYPE_NETWORK,sWICacheDir,0\nPatch.SourceListInfo(&quot;DiskPrompt&quot;) = oMsi.ProductInfo(Patch.ProductCode, &quot;ProductName&quot;)\noWShell.RegWrite sRegHive &amp; sMspClasses &amp; &quot;SourceList\\PackageName&quot;, sPackageName,&quot;REG_SZ&quot;\noWShell.RegWrite sRegHive &amp; sMspClasses &amp; &quot;SourceList\\Media\\100&quot;, sVolumeLabel&amp;&quot;;&quot;&amp;sDiskPrompt,&quot;REG_SZ&quot;\n&#039;Patch.SourceListAddMediaDisk sKey,sVolumeLabel,sDiskPrompt\nEnd If\n\nEnd If &#039;FixMspReg\n\nEnd Sub &#039;EnsurePatchMetadata\n&#039;==============================================================================================\n\nSub FixMspGlobalReg(sPatchCode)\n\nDim sPatchCodeCompressed,sGlobalPatchKey,sValue\n\nOn Error Resume Next\n\nsPatchCodeCompressed = GetCompressedGuid(sPatchCode)\nsGlobalPatchKey = REG_GLOBALCONFIG &amp; &quot;S-1-5-18\\Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\n&#039;Create the registry key\nIf NOT RegKeyExists(HKEY_LOCAL_MACHINE,REG_GLOBALCONFIG &amp; &quot;S-1-5-18\\Patches\\&quot;) Then oReg.CreateKey HKEY_LOCAL_MACHINE,REG_GLOBALCONFIG &amp; &quot;S-1-5-18\\Patches\\&quot;\nIf NOT RegKeyExists(HKEY_LOCAL_MACHINE,sGlobalPatchKey) Then oReg.CreateKey HKEY_LOCAL_MACHINE,sGlobalPatchKey\n\n&#039;Obtain a filename.\n&#039;If the file already exists in the installer cache - use that one. If not use a random filename\nIf dicRepair.Exists(sPatchCode) Then \nIf InStr(LCase(dicRepair.Item(sPatchCode)),LCase(sWICacheDir))&gt; 0 then sValue = dicRepair.Item(sPatchCode) Else sValue = GetRandomMspName\nElse \nsValue = GetRandomMspName\nEnd If\n\n&#039;Create the registry value\noReg.SetStringValue HKEY_LOCAL_MACHINE,sGlobalPatchKey,&quot;LocalPackage&quot;,sValue\n\nEnd Sub &#039;FixMspGlobalReg\n&#039;=======================================================================================================\n\n&#039;Only supports per-machine installations!\nSub UpdateProductVersion(sProductCode,sProductVersion)\n\nDim sProductCodeCompressed,sHive,sGlobalConfigKey\n\nOn Error Resume Next\n\nsProductCodeCompressed = GetCompressedGuid(sProductCode)\nsHive = HKEY_LOCAL_MACHINE\nsGlobalConfigKey = REG_GLOBALCONFIG &amp; &quot;S-1-5-18\\Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\InstallProperties\\&quot;\nIf RegKeyExists(sHive,sGlobalConfigKey) Then oReg.SetStringValue sHive,sGlobalConfigKey,&quot;DisplayVersion&quot;,sProductVersion\n\nEnd Sub &#039;UpdateProductVersion\n&#039;=======================================================================================================\n\nSub UnregisterPatch(Patch)\n\nDim PatchRef,PatchR\n\nDim fReturn\n\nDim sHive,sKey,sPatchCodeCompressed,sProductCodeCompressed,sUserSid,sPatchKey,sProductKey,sPatchList\nDim sGlobalConfigKey,sGlobalPatchKey\n\nDim value\n\nDim i\n\nDim arrMultiSzValues,arrMultiSzNewValues,arrTest\n\nOn Error Resume Next\n\n&#039;Ensure empty variables\nsHive = &quot;&quot; : sKey = &quot;&quot; : sPatchCodeCompressed = &quot;&quot; : sProductCodeCompressed = &quot;&quot; : sUserSid = &quot;&quot;\nsPatchKey = &quot;&quot; : sProductKey = &quot;&quot; : sPatchList = &quot;&quot; \nReDim arrMultiSzNewValues(-1)\ni = -1\n\n&#039;Fill variables\nsPatchCodeCompressed = GetCompressedGuid(Patch.PatchCode)\nsProductCodeCompressed = GetCompressedGuid(Patch.ProductCode)\nsUserSid = Patch.UserSid : If sUserSid = &quot;&quot; Then sUserSid = &quot;S-1-5-18\\&quot; Else sUserSid = sUserSid &amp; &quot;\\&quot;\nsGlobalConfigKey = REG_GLOBALCONFIG &amp; sUserSid &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot; \nsGlobalPatchKey = REG_GLOBALCONFIG &amp; sUserSid &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\n\nIf Err &lt;&gt; 0 Then Exit Sub\n\nSelect Case (Patch.Context)\nCase MSIINSTALLCONTEXT_USERMANAGED &#039;1\nsHive = HKEY_LOCAL_MACHINE\nsPatchKey = REG_PRODUCTPERUSERMANAGED &amp; sUserSid &amp; &quot;Installer\\Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nsProductKey = REG_PRODUCTPERUSERMANAGED &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\n\nCase MSIINSTALLCONTEXT_USERUNMANAGED &#039;2\nsHive = HKEY_CURRENT_USER\nsPatchKey = REG_PRODUCTPERUSER &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nsProductKey = REG_PRODUCTPERUSER &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot;\n\nCase Else &#039;Case MSIINSTALLCONTEXT_MACHINE &#039;4 (Managed)\nsHive = HKEY_LOCAL_MACHINE\nsPatchKey = REG_PRODUCT &amp; &quot;Patches\\&quot; &amp; sPatchCodeCompressed &amp; &quot;\\&quot;\nsProductKey = REG_PRODUCT &amp; &quot;Products\\&quot; &amp; sProductCodeCompressed &amp; &quot;\\Patches\\&quot; \nEnd Select\n\n&#039;Unregister the patch from ProductKey\nIf RegReadMultiStringValue(sHive,sProductKey,&quot;Patches&quot;,arrMultiSzValues) Then\nFor Each value in arrMultiSzValues\nIf Not value = sPatchCodeCompressed Then \ni = i + 1 \nReDim Preserve arrMultiSzNewValues(i)\narrMultiSzNewValues(i) = value\nEnd If\nNext &#039;Value\nEnd If\nfReturn = oReg.GetMultiStringValue(sHive,sProductKey,&quot;Patches&quot;,arrTest)\nIf fReturn = 0 Then\nLog vbTab&amp;vbTab&amp;&quot;Updating value &quot;&amp;HiveString(sHive)&amp;&quot;\\&quot;&amp;sProductKey&amp;&quot;Patches&quot;\nIf NOT fDetectOnly Then oReg.SetMultiStringValue sHive,sProductKey,&quot;Patches&quot;,arrMultiSzNewValues\nElse\nIf fx64 Then\nfReturn = oReg.GetMultiStringValue(sHive,Wow64Key(sHive, sProductKey),&quot;Patches&quot;,arrMultiSzNewValues)\nIf fReturn = 0 Then\nLog vbTab&amp;vbTab&amp;&quot;Updating value &quot;&amp;HiveString(sHive)&amp;&quot;\\&quot;&amp;Wow64Key(sHive, sProductKey)&amp;&quot;Patches&quot;\nIf NOT fDetectOnly Then oReg.SetMultiStringValue sHive,Wow64Key(sHive, sProductKey),&quot;Patches&quot;,arrMultiSzNewValues\nEnd If\nEnd If &#039;fx64\nEnd If\nRegDeleteValue sHive,sProductKey,sPatchCodeCompressed\n\n&#039;Unregister PatchKey\nRegDeleteKey sHive,sPatchKey\n\n&#039;Unregister GlobalConfigKey\nReDim arrMultiSzNewValues(-1)\ni = -1\nIf RegReadMultiStringValue(sHive,sGlobalConfigKey,&quot;AllPatches&quot;,arrMultiSzValues) Then\nFor Each Value in arrMultiSzValues\nIf Not Value = sPatchCodeCompressed Then \ni = i + 1 \nReDim Preserve arrMultiSzNewValues(i)\narrMultiSzNewValues(i) = Value\nEnd If\nNext &#039;Value\nEnd If\nfReturn = oReg.GetMultiStringValue(sHive,sGlobalConfigKey,&quot;AllPatches&quot;,arrTest)\nIf fReturn = 0 Then\nLog vbTab&amp;vbTab&amp;&quot;Updating value &quot;&amp;HiveString(sHive)&amp;&quot;\\&quot;&amp;sGlobalConfigKey&amp;&quot;AllPatches&quot;\nIf NOT fDetectOnly Then oReg.SetMultiStringValue sHive,sGlobalConfigKey,&quot;AllPatches&quot;,arrMultiSzNewValues\nElse\nIf fx64 Then\nfReturn = oReg.GetMultiStringValue(sHive,Wow64Key(sHive, sGlobalConfigKey),&quot;AllPatches&quot;,arrMultiSzNewValues)\nIf fReturn = 0 Then\nLog vbTab&amp;vbTab&amp;&quot;Updating value &quot;&amp;HiveString(sHive)&amp;&quot;\\&quot;&amp;Wow64Key(sHive, sGlobalConfigKey)&amp;&quot;AllPatches&quot;\nIf NOT fDetectOnly Then oReg.SetMultiStringValue sHive,Wow64Key(sHive, sGlobalConfigKey),&quot;AllPatches&quot;,arrMultiSzNewValues\nEnd If\nEnd If &#039;fx64\nEnd If\n\nRegDeleteKey HKEY_LOCAL_MACHINE,sGlobalConfigKey&amp;sPatchCodeCompressed &amp; &quot;\\&quot;\n\n&#039;Unregister sGlobalPatchKey\nRegDeleteKey HKEY_LOCAL_MACHINE,sGlobalPatchKey\nEnd Sub\n&#039;=======================================================================================================\n\nFunction ApplyPatch(sProductCode,sPatches)\n\nDim sReturn,sCmd\n\nOn Error Resume Next\n\n&#039;Build the patch apply command\nsCmd = &quot;msiexec.exe \/i &quot; &amp; sProductCode &amp; _\n&quot; PATCH=&quot;&amp;chr(34)&amp;Mid(sPatches,2)&amp;chr(34)&amp; _\n&quot; REBOOT=ReallySuppress&quot; &amp; _\n&quot; \/qb-&quot; &amp; _\n&quot; \/l*v+ %temp%\\&quot; &amp; sProductCode &amp; &quot;_MspApply.log&quot;\nsTmp = &quot;Calling msiexec to apply patch(es): &quot; &amp; sCmd\nIf fDetectOnly Then\nsPatches = Replace(sPatches,&quot;;&quot;,vbCrLf&amp;vbTab&amp;vbTab)\nsTmp = &quot;Applicable patch(es): &quot;&amp;sPatches\nEnd If &#039;fDetectOnly\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\n&#039;Execute the patch apply command\nIf NOT fDetectOnly Then \nsReturn = CStr(oWShell.Run(sCmd, 0, True))\nApplyPatch = MsiexecRetVal(sReturn)\nsTmp = &quot;Msiexec returned with code: &quot; &amp; sReturn &amp;&quot; &quot;&amp; MsiexecRetval(sReturn)\nLog vbTab&amp;&quot;Debug: &quot;&amp; sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nfRebootRequired = fRebootRequired OR (sReturn = &quot;3010&quot;)\nIf NOT (sReturn = &quot;0&quot; OR sReturn = &quot;3010&quot;) Then\n&#039;Patch attempt failed.\nDim arrPatch,Patch\narrPatch = Split(Mid(sPatches,2),&quot;;&quot;)\nIf UBound(arrPatch) &gt; 0 Then\n&#039;Fall back to apply one patch at a time\nFor Each Patch in arrPatch\nsCmd = &quot;msiexec.exe \/i &quot; &amp; sProductCode &amp; _\n&quot; PATCH=&quot;&amp;chr(34)&amp;Patch&amp;chr(34)&amp; _\n&quot; REBOOT=ReallySuppress&quot; &amp; _\n&quot; \/qb-&quot; &amp; _\n&quot; \/l*v+ %temp%\\&quot; &amp; sProductCode &amp; &quot;_MspApply.log&quot;\nsTmp = &quot; Fallback. Calling msiexec to apply patch: &quot; &amp; sCmd\nLog vbTab&amp;&quot;Debug: &quot;&amp;sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nsReturn = CStr(oWShell.Run(sCmd, 0, True))\nsTmp = &quot; Msiexec returned with code: &quot; &amp; sReturn &amp;&quot; &quot;&amp; MsiexecRetval(sReturn)\nLog vbTab&amp;&quot;Debug: &quot;&amp; sTmp\nLogSummary sProductCode,vbTab&amp;sTmp\nIf fCscript Then wscript.echo vbTab&amp;vbTab&amp;sTmp\nfRebootRequired = fRebootRequired OR (sReturn = &quot;3010&quot;)\nNext &#039;Patch\nEnd If &#039;arrPatch.Count &gt; 1\nEnd If &#039;sReturn \nEnd If &#039;NOT fDetectOnly\n\nEnd Function &#039;ApplyPatch\n&#039;=======================================================================================================\n\n&#039;Return the primary keys of a table by using the PrimaryKeys property of the database object\n&#039;in SQL ready syntax \nFunction GetPrimaryTableKeys(MsiDb,sTable)\nDim iKeyCnt\nDim sPrimaryTmp\nDim PrimaryKeys\nOn Error Resume Next\n\nsPrimaryTmp = &quot;&quot;\nSet PrimaryKeys = MsiDb.PrimaryKeys(sTable)\nFor iKeyCnt = 1 To PrimaryKeys.FieldCount\nsPrimaryTmp = sPrimaryTmp &amp; &quot;`&quot;&amp;PrimaryKeys.StringData(iKeyCnt)&amp;&quot;`, &quot;\nNext &#039;iKeyCnt\nGetPrimaryTableKeys = Left(sPrimaryTmp,Len(sPrimaryTmp)-2)\nEnd Function &#039;GetPrimaryTableKeys\n&#039;=======================================================================================================\n\n&#039;Return the Column schema definition of a table in SQL ready syntax\nFunction GetTableColumnDef(MsiDb,sTable)\nOn Error Resume Next\nDim sQuery,sColDefTmp\nDim View,ColumnNames,ColumnTypes\nDim iColCnt\n\n&#039;Get the ColumnInfo details\nsColDefTmp = &quot;&quot;\nsQuery = &quot;SELECT * FROM &quot; &amp; sTable\nSet View = MsiDb.OpenView(sQuery)\nView.Execute\nSet ColumnNames = View.ColumnInfo(MSICOLUMNINFONAMES)\nSet ColumnTypes = View.ColumnInfo(MSICOLUMNINFOTYPES)\nFor iColCnt = 1 To ColumnNames.FieldCount\nsColDefTmp = sColDefTmp &amp; ColDefToSql(ColumnNames.StringData(iColCnt),ColumnTypes.StringData(iColCnt)) &amp; &quot;, &quot;\nNext &#039;iColCnt\nView.Close\nGetTableColumnDef = Left(sColDefTmp,Len(sColDefTmp)-2)\n\nEnd Function &#039;GetTableColumnDef\n&#039;=======================================================================================================\n\n&#039;Return the Column header names\nFunction GetTableColumnHeaders(MsiDb,sTable)\nOn Error Resume Next\nDim sQuery,sColDefTmp\nDim View,ColumnNames,ColumnTypes\nDim iColCnt\n\n&#039;Get the ColumnInfo details\nsColDefTmp = &quot;&quot;\nsQuery = &quot;SELECT * FROM &quot; &amp; sTable\nSet View = MsiDb.OpenView(sQuery)\nView.Execute\nSet ColumnNames = View.ColumnInfo(MSICOLUMNINFONAMES)\nFor iColCnt = 1 To ColumnNames.FieldCount\nsColDefTmp = sColDefTmp &amp; &quot;,&quot; &amp; ColumnNames.StringData(iColCnt)\nNext &#039;iColCnt\nView.Close\nIf NOT sColDefTmp=&quot;&quot; Then GetTableColumnHeaders = Mid(sColDefTmp,2) Else GetTableColumnHeaders=&quot;&quot;\n\nEnd Function &#039;GetTableColumnHeaders\n&#039;=======================================================================================================\n\n&#039;Translate the column definition fields into SQL syntax\nFunction ColDefToSql(sColName,sColType)\nOn Error Resume Next\n\nDim iLen\nDim sRight,sLeft, sSqlTmp\n\niLen = Len(sColType)\nsRight = Right(sColType,iLen-1)\nsLeft = Left(sColType,1)\nsSqlTmp = &quot;`&quot;&amp;sColName&amp;&quot;`&quot;\nSelect Case sLeft\nCase &quot;s&quot;,&quot;S&quot;\n&#039;s? String, variable length (?=1-255) -&gt; CHAR(#) or CHARACTER(#)\n&#039;s0 String, variable length -&gt; LONGCHAR\nIf sRight=&quot;0&quot; Then sSqlTmp = sSqlTmp &amp; &quot; LONGCHAR&quot; Else sSqlTmp = sSqlTmp &amp; &quot; CHAR(&quot;&amp;sRight&amp;&quot;)&quot;\nIf sLeft = &quot;s&quot; Then sSqlTmp = sSqlTmp &amp; &quot; NOT NULL&quot;\nCase &quot;l&quot;,&quot;L&quot;\n&#039;CHAR(#) LOCALIZABLE or CHARACTER(#) LOCALIZABLE\nIf sRight=&quot;0&quot; Then sSqlTmp = sSqlTmp &amp; &quot; LONGCHAR&quot; Else sSqlTmp = sSqlTmp &amp; &quot; CHAR(&quot;&amp;sRight&amp;&quot;)&quot;\nIf sLeft = &quot;l&quot; Then sSqlTmp = sSqlTmp &amp; &quot; NOT NULL&quot;\nIf sRight=&quot;0&quot; Then sSqlTmp = sSqlTmp &amp; &quot; LOCALIZABLE&quot; Else sSqlTmp = sSqlTmp &amp; &quot; LOCALIZABLE&quot;\nCase &quot;i&quot;,&quot;I&quot;\n&#039;i2 Short integer \n&#039;i4 Long integer \nIf sRight=&quot;2&quot; Then sSqlTmp = sSqlTmp &amp; &quot; SHORT&quot; Else sSqlTmp = sSqlTmp &amp; &quot; LONG&quot;\nIf sLeft = &quot;i&quot; Then sSqlTmp = sSqlTmp &amp; &quot; NOT NULL&quot;\nCase &quot;v&quot;,&quot;V&quot;\n&#039;v0 Binary Stream \nsSqlTmp = sSqlTmp &amp; &quot; OBJECT&quot;\nIf sLeft = &quot;v&quot; Then sSqlTmp = sSqlTmp &amp; &quot; NOT NULL&quot;\nCase &quot;g&quot;,&quot;G&quot;\n&#039;g? Temporary string (?=0-255)\nCase &quot;j&quot;,&quot;J&quot;\n&#039;j? Temporary integer (?=0,1,2,4)) \nCase &quot;o&quot;,&quot;O&quot;\n&#039;O0 Temporary object \nCase Else\nEnd Select\n\nColDefToSql = sSqlTmp\n\nEnd Function &#039;ColDefToSql\n&#039;=======================================================================================================\n\n&#039;Registry Helper Routines\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\n\n&#039;=======================================================================================================\n\n&#039;Register context menu\nSub RegisterShellExt\n\n&#039;Ensure to unregister old contents first\nUnRegisterShellExt\n\n&#039;Register \noReg.CreateKey HKCR,&quot;Msi.Patch&quot;\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell&quot;\n\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ApplyPatch&quot;\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ApplyPatch\\command&quot;\noReg.SetStringValue HKCR,&quot;Msi.Patch\\shell\\OPUtil ApplyPatch\\command&quot;,,&quot;wscript &quot;&amp;chr(34)&amp;wscript.ScriptFullName&amp;chr(34)&amp;&quot; \/ContextMenu \/ApplyPatch=&quot;&amp;chr(34)&amp;&quot;%1%&quot;&amp;chr(34)\n\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil CabExtract&quot;\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil CabExtract\\command&quot;\noReg.SetStringValue HKCR,&quot;Msi.Patch\\shell\\OPUtil CabExtract\\command&quot;,,&quot;wscript &quot;&amp;chr(34)&amp;wscript.ScriptFullName&amp;chr(34)&amp;&quot; \/ContextMenu \/CabExtract=&quot;&amp;chr(34)&amp;&quot;%1%&quot;&amp;chr(34)\n\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil RemovePatch&quot;\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil RemovePatch\\command&quot;\noReg.SetStringValue HKCR,&quot;Msi.Patch\\shell\\OPUtil RemovePatch\\command&quot;,,&quot;wscript &quot;&amp;chr(34)&amp;wscript.ScriptFullName&amp;chr(34)&amp;&quot; \/ContextMenu \/RemovePatch=&quot;&amp;chr(34)&amp;&quot;%1%&quot;&amp;chr(34)\n\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch&quot;\noReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch\\command&quot;\noReg.SetStringValue HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch\\command&quot;,,&quot;wscript &quot;&amp;chr(34)&amp;wscript.ScriptFullName&amp;chr(34)&amp;&quot; \/ContextMenu \/ViewPatch=&quot;&amp;chr(34)&amp;&quot;%1%&quot;&amp;chr(34)\n\n&#039; oReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch (DeepScan)&quot;\n&#039; oReg.CreateKey HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch (DeepScan)\\command&quot;\n&#039; oReg.SetStringValue HKCR,&quot;Msi.Patch\\shell\\OPUtil ViewPatch (DeepScan)\\command&quot;,,&quot;wscript &quot;&amp;chr(34)&amp;wscript.ScriptFullName&amp;chr(34)&amp;&quot; \/ContextMenu \/DeepScan \/ViewPatch=&quot;&amp;chr(34)&amp;&quot;%1%&quot;&amp;chr(34)\n\nEnd Sub &#039;RegisterShellExt\n&#039;=======================================================================================================\n\n&#039;Register context menu\nSub UnRegisterShellExt\n\nDim arrKeys\nDim Key\nDim sSubKeyName\n\nsSubKeyName = &quot;Msi.Patch\\shell\\&quot;\nIf (oReg.EnumKey(HKCR,sSubKeyName,arrKeys)=0) AND IsArray(arrKeys) Then\nFor Each Key in arrKeys\nIf InStr(Key,&quot;OPUtil&quot;)&gt;0 Then RegDeleteKey HKCR,sSubKeyName&amp;Key\nNext &#039;Key\nEnd If\n\nEnd Sub &#039;RegisterShellExt\n&#039;=======================================================================================================\n\nFunction RegKeyExists(hDefKey,sSubKeyName)\nOn Error Resume Next\nDim arrKeys\nRegKeyExists = False\nIf oReg.EnumKey(hDefKey,sSubKeyName,arrKeys) = 0 Then RegKeyExists = True\nEnd Function\n&#039;=======================================================================================================\n\nFunction HiveString(hDefKey)\nOn Error Resume Next\nSelect Case hDefKey\nCase HKCR : HiveString = &quot;HKEY_CLASSES_ROOT&quot;\nCase HKCU : HiveString = &quot;HKEY_CURRENT_USER&quot;\nCase HKLM : HiveString = &quot;HKEY_LOCAL_MACHINE&quot;\nCase HKU : HiveString = &quot;HKEY_USERS&quot;\nCase Else : HiveString = hDefKey\nEnd Select\nEnd Function\n&#039;=======================================================================================================\n\nFunction RegValExists(hDefKey,sSubKeyName,sName)\nDim arrValueTypes, arrValueNames\nDim i\n\nOn Error Resume Next\nRegValExists = False\nIf Not RegKeyExists(hDefKey,sSubKeyName) Then Exit Function\nIf oReg.EnumValues(hDefKey,sSubKeyName,arrValueNames,arrValueTypes) = 0 AND IsArray(arrValueNames) Then\nFor i = 0 To UBound(arrValueNames) \nIf LCase(arrValueNames(i)) = Trim(LCase(sName)) Then RegValExists = True\nNext \nEnd If &#039;oReg.EnumValues\nEnd Function\n&#039;=======================================================================================================\n\nFunction RegReadMultiStringValue(hDefKey,sSubKeyName,sName,arrValues)\nDim RetVal\n\nOn Error Resume Next\nRetVal = oReg.GetMultiStringValue(hDefKey,sSubKeyName,sName,arrValues)\nIf Not RetVal = 0 AND fx64 Then RetVal = oReg.GetMultiStringValue(hDefKey,Wow64Key(hDefKey, sSubKeyName),sName,arrValues)\n\nRegReadMultiStringValue = (RetVal = 0 AND IsArray(arrValues))\nEnd Function &#039;RegReadMultiStringValue\n&#039;=======================================================================================================\n\n&#039;Enumerate a registry key to return all values\nFunction RegEnumValues(hDefKey,sSubKeyName,arrNames, arrTypes)\nDim RetVal, RetVal64\nDim arrNames32, arrNames64, arrTypes32, arrTypes64\n\nOn Error Resume Next\nIf fx64 Then\nRetVal = oReg.EnumValues(hDefKey,sSubKeyName,arrNames32,arrTypes32)\nRetVal64 = oReg.EnumValues(hDefKey,Wow64Key(hDefKey, sSubKeyName),arrNames64,arrTypes64)\nIf (RetVal = 0) AND (Not RetVal64 = 0) AND IsArray(arrNames32) AND IsArray(arrTypes32) Then \narrNames = arrNames32\narrTypes = arrTypes32\nEnd If\nIf (Not RetVal = 0) AND (RetVal64 = 0) AND IsArray(arrNames64) AND IsArray(arrTypes64) Then \narrNames = arrNames64\narrTypes = arrTypes64\nEnd If\nIf (RetVal = 0) AND (RetVal64 = 0) AND IsArray(arrNames32) AND IsArray(arrNames64) AND IsArray(arrTypes32) AND IsArray(arrTypes64) Then \narrNames = RemoveDuplicates(Split((Join(arrNames32,&quot;\\&quot;) &amp; &quot;\\&quot; &amp; Join(arrNames64,&quot;\\&quot;)),&quot;\\&quot;))\narrTypes = RemoveDuplicates(Split((Join(arrTypes32,&quot;\\&quot;) &amp; &quot;\\&quot; &amp; Join(arrTypes64,&quot;\\&quot;)),&quot;\\&quot;))\nEnd If\nElse\nRetVal = oReg.EnumValues(hDefKey,sSubKeyName,arrNames,arrTypes)\nEnd If &#039;fx64\nRegEnumValues = ((RetVal = 0) OR (RetVal64 = 0)) AND IsArray(arrNames) AND IsArray(arrTypes)\nEnd Function &#039;RegEnumValues\n&#039;=======================================================================================================\n\n&#039;Enumerate a registry key to return all subkeys\nFunction RegEnumKey(hDefKey,sSubKeyName,arrKeys)\nDim RetVal, RetVal64\nDim arrKeys32, arrKeys64\n\nOn Error Resume Next\nIf fx64 Then\nRetVal = oReg.EnumKey(hDefKey,sSubKeyName,arrKeys32)\nRetVal64 = oReg.EnumKey(hDefKey,Wow64Key(hDefKey, sSubKeyName),arrKeys64)\nIf (RetVal = 0) AND (Not RetVal64 = 0) AND IsArray(arrKeys32) Then arrKeys = arrKeys32\nIf (Not RetVal = 0) AND (RetVal64 = 0) AND IsArray(arrKeys64) Then arrKeys = arrKeys64\nIf (RetVal = 0) AND (RetVal64 = 0) Then \nIf IsArray(arrKeys32) AND IsArray (arrKeys64) Then \narrKeys = RemoveDuplicates(Split((Join(arrKeys32,&quot;\\&quot;) &amp; &quot;\\&quot; &amp; Join(arrKeys64,&quot;\\&quot;)),&quot;\\&quot;))\nElseIf IsArray(arrKeys64) Then\narrKeys = arrKeys64\nElse\narrKeys = arrKeys32\nEnd If\nEnd If\nElse\nRetVal = oReg.EnumKey(hDefKey,sSubKeyName,arrKeys)\nEnd If &#039;fx64\nRegEnumKey = ((RetVal = 0) OR (RetVal64 = 0)) AND IsArray(arrKeys)\nEnd Function &#039;RegEnumKey\n&#039;=======================================================================================================\n\n&#039;Wrapper around oReg.DeleteValue to handle 64 bit\nSub RegDeleteValue(hDefKey, sSubKeyName, sName)\nDim sWow64Key\n\nOn Error Resume Next\nIf RegValExists(hDefKey,sSubKeyName,sName) Then\nOn Error Resume Next\nLog vbTab&amp;vbTab&amp;&quot;Deleting value &quot;&amp;HiveString(hDefKey)&amp;&quot;\\&quot;&amp;sSubKeyName&amp;sName\nIf NOT fDetectOnly Then oReg.DeleteValue hDefKey, sSubKeyName, sName\nOn Error Goto 0\nEnd If &#039;RegValExists\nIf fx64 Then \nsWow64Key = Wow64Key(hDefKey, sSubKeyName)\nIf RegValExists(hDefKey,sWow64Key,sName) Then\nOn Error Resume Next\nLog vbTab&amp;vbTab&amp;&quot;Deleting value &quot;&amp;HiveString(hDefKey)&amp;&quot;\\&quot;&amp;sWow64Key&amp;sName\nIf NOT fDetectOnly Then oReg.DeleteValue hDefKey, sWow64Key, sName\nOn Error Goto 0\nEnd If &#039;RegValExists\nEnd If\nEnd Sub &#039;RegDeleteValue\n&#039;=======================================================================================================\n\n&#039;Wrappper around RegDeleteKeyEx to handle 64bit scenarios\nSub RegDeleteKey(hDefKey, sSubKeyName)\nDim sWow64Key\n\nOn Error Resume Next\nIf RegKeyExists(hDefKey, sSubKeyName) Then\n&#039;Get the list of patches for the product\n\nOn Error Resume Next\nRegDeleteKeyEx hDefKey, sSubKeyName\nOn Error Goto 0\nEnd If &#039;RegKeyExists\nIf fx64 Then \nsWow64Key = Wow64Key(hDefKey, sSubKeyName)\nIf RegKeyExists(hDefKey,sWow64Key) Then\nOn Error Resume Next\nRegDeleteKeyEx hDefKey, sWow64Key\nOn Error Goto 0\nEnd If &#039;RegKeyExists\nEnd If\nEnd Sub &#039;RegDeleteKey\n&#039;=======================================================================================================\n\n&#039;Recursively delete a registry structure\nSub RegDeleteKeyEx(hDefKey, sSubKeyName) \nDim arrSubkeys\nDim sSubkey\n\nOn Error Resume Next\nDo While InStr(sSubKeyName,&quot;\\\\&quot;)&gt;0\nsSubKeyName = Replace(sSubKeyName,&quot;\\\\&quot;,&quot;\\&quot;)\nLoop\nIf Not Right(sSubKeyName,1)=&quot;\\&quot; Then sSubKeyName=sSubKeyName&amp;&quot;\\&quot;\noReg.EnumKey hDefKey, sSubKeyName, arrSubkeys \nIf IsArray(arrSubkeys) Then \nFor Each sSubkey In arrSubkeys \nRegDeleteKeyEx hDefKey, sSubKeyName &amp; sSubkey &amp; &quot;\\&quot;\nNext \nEnd If \nLog vbTab&amp;vbTab&amp;&quot;Deleting key &quot;&amp;HiveString(hDefKey)&amp;&quot;\\&quot;&amp;sSubKeyName\nIf NOT fDetectOnly Then oReg.DeleteKey hDefKey, sSubKeyName \nEnd Sub &#039;RegDeleteKeyEx\n&#039;=======================================================================================================\n\n&#039;Return the alternate regkey location on 64bit environment\nFunction Wow64Key(hDefKey, sSubKeyName)\nDim iPos\n\nOn Error Resume Next\nSelect Case hDefKey\nCase HKCU\nIf Left(sSubKeyName,17) = &quot;Software\\Classes\\&quot; Then\nWow64Key = Left(sSubKeyName,17) &amp; &quot;Wow6432Node\\&quot; &amp; Right(sSubKeyName,Len(sSubKeyName)-17)\nElse\niPos = InStr(sSubKeyName,&quot;\\&quot;)\nWow64Key = Left(sSubKeyName,iPos) &amp; &quot;Wow6432Node\\&quot; &amp; Right(sSubKeyName,Len(sSubKeyName)-iPos)\nEnd If\n\nCase HKLM\nIf Left(sSubKeyName,17) = &quot;Software\\Classes\\&quot; Then\nWow64Key = Left(sSubKeyName,17) &amp; &quot;Wow6432Node\\&quot; &amp; Right(sSubKeyName,Len(sSubKeyName)-17)\nElse\niPos = InStr(sSubKeyName,&quot;\\&quot;)\nWow64Key = Left(sSubKeyName,iPos) &amp; &quot;Wow6432Node\\&quot; &amp; Right(sSubKeyName,Len(sSubKeyName)-iPos)\nEnd If\n\nCase Else\nWow64Key = &quot;Wow6432Node\\&quot; &amp; sSubKeyName\n\nEnd Select &#039;hDefKey\nEnd Function &#039;Wow64Key\n&#039;=======================================================================================================\n\n&#039;File Helper Routines\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\n\n&#039;=======================================================================================================\n\n&#039;Function to compare two numbers of unspecified format\n&#039;Return values:\n&#039;Left file version is lower than right file version -1\n&#039;Left file version is identical to right file version 0\n&#039;Left file version is higher than right file version 1\n&#039;Invalid comparison 2\n\nFunction CompareVersion(sFile1, sFile2, bAllowBlanks)\n\nDim file1, file2\nDim sDelimiter\nDim iCnt, iAsc, iMax, iF1, iF2\nDim bLEmpty,bREmpty\n\nCompareVersion = 0\nbLEmpty = False\nbREmpty = False\n\n&#039;Ensure valid inputs values\nOn Error Resume Next\nIf IsEmpty(sFile1) Then bLEmpty = True\nIf IsEmpty(sFile2) Then bREmpty = True\nIf sFile1 = &quot;&quot; Then bLEmpty = True\nIf sFile2 = &quot;&quot; Then bREmpty = True\n\n&#039;Don&#039;t allow alpha characters\nIf Not bLEmpty Then\nFor iCnt = 1 To Len(sFile1)\niAsc = Asc(UCase(Mid(sFile1,iCnt,1)))\nIf (iAsc&gt;64) AND (iAsc&lt;91) Then\nCompareVersion = 2\nExit Function\nEnd If\nNext &#039;iCnt\nEnd If\nIf Not bREmpty Then\nFor iCnt = 1 To Len(sFile2)\niAsc = Asc(UCase(Mid(sFile2,iCnt,1)))\nIf (iAsc&gt;64) AND (iAsc&lt;91) Then\nCompareVersion = 2\nExit Function\nEnd If\nNext &#039;iCnt\nEnd If\n\nIf bLEmpty AND (NOT bREmpty) Then\nIf bAllowBlanks Then CompareVersion = -1 Else CompareVersion = 2\nExit Function\nEnd If\n\nIf (NOT bLEmpty) AND bREmpty Then\nIf bAllowBlanks Then CompareVersion = 1 Else CompareVersion = 2\nExit Function\nEnd If\n\nIf bLEmpty AND bREmpty Then\nIf bAllowBlanks Then CompareVersion = 0 Else CompareVersion = 2\nExit Function\nEnd If\n\n&#039;If Files are identical we&#039;re already done\nIf sFile1 = sFile2 Then Exit Function\n\n&#039;Split the VersionString\nfile1 = Split(sFile1,Delimiter(sFile1))\nfile2 = Split(sFile2,Delimiter(sFile2))\n\n&#039;Ensure we get the lower count\niMax = UBound(file1)\nCompareVersion = -1\nIf iMax &gt; UBound(file2) Then \niMax = UBound(file2)\nCompareVersion = 1\nEnd If\n\n&#039;Compare the file versions\nFor iCnt = 0 To iMax\niF1 = CLng(file1(iCnt))\niF2 = CLng(file2(iCnt))\nIf iF1 &gt; iF2 Then\nCompareVersion = 1\nExit For\nElseIf iF1 &lt; iF2 Then\nCompareVersion = -1\nExit For\nEnd If\nNext &#039;iCnt\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Use WI ProvideAssembly function to identify the path for an assembly.\n&#039;Returns the path to the file if the file exists.\n&#039;Returns an empty string if file does not exist\n\nFunction GetAssemblyPath(sLfn,sKeyPath,sDir)\nOn Error Resume Next\nDim sFile,sFolder,sExt,sRoot,sName\nDim arrTmp\n\n&#039;Defaults\nGetAssemblyPath=&quot;&quot;\nsFile=&quot;&quot; : sFolder=&quot;&quot; : sExt=&quot;&quot; : sRoot=&quot;&quot; : sName=&quot;&quot;\n\n&#039;The componentpath should already point to the correct folder\n&#039;except for components with a registry keypath element.\n&#039;In that case tweak the directory folder to match\nIf Left(sKeyPath,1)=&quot;0&quot; Then\nsFolder = sDir\nsFolder = oWShell.ExpandEnvironmentStrings(&quot;%SYSTEMROOT%&quot;)&amp;Mid(sFolder,InStr(LCase(sFolder),&quot;\\winsxs\\&quot;))\nsFile = sLfn\nEnd If &#039;Left(sKeyPath,1)=&quot;0&quot;\n\n&#039;Figure out the correct file reference\nIf sFolder = &quot;&quot; Then sFolder = Left(sKeyPath,InStrRev(sKeyPath,&quot;\\&quot;))\nsRoot = Left(sFolder,InStrRev(sFolder,&quot;\\&quot;,Len(sFolder)-1))\narrTmp = Split(sFolder,&quot;\\&quot;)\nIf IsArray(arrTmp) AND UBound(arrTmp)&gt;0 Then sName = arrTmp(UBound(arrTmp)-1)\nIf sFile = &quot;&quot; Then sFile = Right(sKeyPath,Len(sKeyPath)-InStrRev(sKeyPath,&quot;\\&quot;))\nIf oFso.FileExists(sFolder&amp;sLfn) Then \nsFile = sLfn\nElse\n&#039;Handle .cat, .manifest and .policy files\nIf InStr(sLfn,&quot;.&quot;)&gt;0 Then\nsExt = Mid(sLfn,InStrRev(sLfn,&quot;.&quot;))\nSelect Case LCase(sExt)\nCase &quot;.cat&quot;\nsFile = Left(sFile,InStrRev(sFile,&quot;.&quot;))&amp;&quot;cat&quot;\nIf Not oFso.FileExists(sFolder&amp;sFile) Then\n&#039;Check Manifest folder\nIf oFso.FileExists(sRoot&amp;&quot;Manifests\\&quot;&amp;sName&amp;&quot;.cat&quot;) Then\nsFolder = sRoot&amp;&quot;Manifests\\&quot;\nsFile = sName&amp;&quot;.cat&quot;\nElse\nIf oFso.FileExists(sRoot&amp;&quot;Policies\\&quot;&amp;sName&amp;&quot;.cat&quot;) Then\nsFolder = sRoot&amp;&quot;Policies\\&quot;\nsFile = sName&amp;&quot;.cat&quot;\nEnd If\nEnd If\nEnd If\nCase &quot;.manifest&quot;\nsFile = Left(sFile,InStrRev(sFile,&quot;.&quot;))&amp;&quot;manifest&quot;\nIf oFso.FileExists(sRoot&amp;&quot;Manifests\\&quot;&amp;sName&amp;&quot;.manifest&quot;) Then\nsFolder = sRoot&amp;&quot;Manifests\\&quot;\nsFile = sName&amp;&quot;.manifest&quot;\nEnd If\nCase &quot;.policy&quot;\nIf iVersionNT &lt; 600 Then\nsFile = Left(sFile,InStrRev(sFile,&quot;.&quot;))&amp;&quot;policy&quot;\nIf oFso.FileExists(sRoot&amp;&quot;Policies\\&quot;&amp;sName&amp;&quot;.policy&quot;) Then\nsFolder = sRoot&amp;&quot;Policies\\&quot;\nsFile = sName&amp;&quot;.policy&quot;\nEnd If\nElse\nsFile = Left(sFile,InStrRev(sFile,&quot;.&quot;))&amp;&quot;manifest&quot;\nIf oFso.FileExists(sRoot&amp;&quot;Manifests\\&quot;&amp;sName&amp;&quot;.manifest&quot;) Then\nsFolder = sRoot&amp;&quot;Manifests\\&quot;\nsFile = sName&amp;&quot;.manifest&quot;\nEnd If\nEnd If\nCase Else\nEnd Select\n\nEnd If &#039;InStr(sFile,&quot;.&quot;)&gt;0\nEnd If\n\nGetAssemblyPath = sFolder&amp;sFile\n\nEnd Function &#039;GetAssemblyPath\n&#039;=======================================================================================================\n\n&#039;Routine to check if it&#039;s required to extract .msp files first\nSub CheckPatchExtract\n\nConst COL_FILEDESCRIPTION = 34\nConst COL_FILEVERSION = 145\n\nDim File, location\nDim iMspCnt,iExeCnt\n\nFor Each location in arrUpdateLocations\nIf NOT location = sWiCacheDir Then\niMspCnt = 0 : iExeCnt = 0\nFor Each File in oFso.GetFolder(location).Files\nIf LCase(Right(File.Name,4))=&quot;.msp&quot; Then iMspCnt=iMspCnt+1\nIf LCase(Right(File.Name,4))=&quot;.exe&quot; Then iExeCnt=iExeCnt+1\nNext &#039;File\nIf (iMspCnt=0) AND (iExeCnt&gt;0) Then\nFor Each File in oFso.GetFolder(location).Files\nIf LCase(Right(File.Name,4))=&quot;.exe&quot; Then \nIf InStr(LCase(GetDetailsOf(File,COL_FILEDESCRIPTION)),&quot;(kb&quot;)&gt;0 Then\nExtractPatch File\nEnd If\nEnd If\nNext &#039;File\nEnd If\nEnd If &#039;sWiCacheDir\nNext &#039;location\n\nEnd Sub &#039;CheckPatchExtract\n&#039;=======================================================================================================\n\nFunction GetDetailsOf(File,iColumn)\nDim oFolder,oFolderItem\n\nset oFolder = oShellApp.NameSpace(File.ParentFolder.Path)\n\nIf (NOT oFolder Is Nothing) Then\nSet oFolderItem = oFolder.ParseName(File.Name)\nIf (NOT oFolderItem Is Nothing) Then\nGetDetailsOf = oFolder.GetDetailsOf(oFolderItem, iColumn)\nEnd If\nEnd If\nEnd Function &#039;GetDetailsOf\n&#039;=======================================================================================================\n\nSub ExtractPatch (File)\n\nDim sCmd,sReturn\n\nOn Error Resume Next\n\nsCmd = chr(34)&amp;File.Path&amp;chr(34) &amp; &quot; \/extract:&quot;&amp;chr(34)&amp;File.ParentFolder.Path&amp;chr(34)&amp;&quot; \/quiet&quot; \nsReturn = oWShell.Run(sCmd,1,True)\nsTmp = vbTab&amp;&quot;Extracting patch &quot;&amp;File.Name&amp;&quot; returned: &quot;&amp;sReturn&amp;&quot; &quot;&amp; ExtractorRetval(sReturn)\nLog sTmp\nIf fCscript Then wscript.echo sTmp\nEnd Sub\n&#039;=======================================================================================================\n\n&#039;Query Wmi to identify local hard disks.\n&#039;The result is stored in a global dic array\n\nSub FindLocalDisks\n\nDim LogicalDisks,Disk\n\nOn Error Resume Next\n\nSet LogicalDisks = oWmiLocal.ExecQuery(&quot;Select * from Win32_LogicalDisk&quot;)\nFor Each Disk in LogicalDisks\nIf Disk.DriveType = DISK_LOCAL Then dicLocalDisks.Add Disk.DeviceID,DISK_LOCAL\nNext &#039;Disk\n\nEnd Sub &#039;FindLocalDisks\n&#039;=======================================================================================================\n\nSub Log(sLog)\nLogStream.WriteLine sLog\nEnd Sub &#039;Log\n&#039;=======================================================================================================\n\nFunction GetRandomMspName()\n\nDim sRandom\n\nDim iHigh,iLow, iRnd\n\nOn Error Resume Next\n\niHigh = 268365550\niLow = 1048576\n\nRandomize\nsRandom = sWICacheDir\niRnd = Rnd\nsRandom = sWICacheDir &amp; LCase(Hex((iHigh-iLow + 1) * Rnd + iLow))&amp;&quot;.msp&quot;\nWhile oFso.FileExists(sRandom)\nRandomize\nsRandom = sWICacheDir &amp; LCase(Hex((iHigh-iLow + 1) * Rnd + iLow))&amp;&quot;.msp&quot;\nWend\n\nGetRandomMspName = sRandom\nEnd Function &#039;GetRandomMspName\n&#039;=======================================================================================================\n\n&#039;String Helper Routines\n&#039;\u2014\u2014\u2014\u2014\u2014\u2014\u2014\u2014\n\n&#039;=======================================================================================================\n\nSub LogSummary(sProductCode,sLog)\nOn Error Resume Next\nIf dicSummary.Exists(sProductCode) Then\ndicSummary.Item(sProductCode) = dicSummary.Item(sProductCode)&amp;sLog&amp;vbCrLf\nElse\ndicSummary.Add sProductCode,vbCrLf&amp;sLog&amp;vbCrLf\nEnd If\nEnd Sub &#039;Log\n&#039;=======================================================================================================\n\n&#039;Translate the Office build to the SP level\n&#039;Possible return values are RTM,SP1,SP2,SP3 or &quot;&quot;\nFunction GetSpLevel(sBuild)\n\nDim arrVersionString\n\nGetSpLevel = &quot;&quot;\n\narrVersionString = Split(sBuild,&quot;.&quot;)\nIf NOT IsArray(arrVersionString) Then Exit Function\nIf NOT UBound(arrVersionString)&gt;1 Then Exit Function &#039;Require &quot;major.minor.build&quot; format\n\nSelect Case arrVersionString(0) &#039;BuildMajor\nCase &quot;14&quot;\nCase &quot;12&quot;\nSelect Case arrVersionstring(2)\nCase &quot;4518&quot; : GetSpLevel = &quot; - RTM&quot;\nCase &quot;6213&quot;,&quot;6215&quot;,&quot;6219&quot; : GetSpLevel = &quot; - SP1&quot;\nCase &quot;6425&quot; : GetSpLevel = &quot; - SP2&quot;\nCase Else\nEnd Select\nCase &quot;11&quot;\nSelect Case arrVersionstring(2)\nCase &quot;3216&quot;,&quot;5510&quot;,&quot;5614&quot; : GetSpLevel = &quot; - RTM&quot;\nCase &quot;4301&quot;,&quot;6353&quot;,&quot;6355&quot;,&quot;6361&quot;,&quot;6707&quot; : GetSpLevel = &quot; - SP1&quot;\nCase &quot;7969&quot; : GetSpLevel = &quot; - SP2&quot;\nCase &quot;8173&quot; : GetSpLevel = &quot; - SP3&quot;\nCase Else\nEnd Select\nCase &quot;10&quot;\nSelect Case arrVersionstring(2)\nCase &quot;525&quot;,&quot;2623&quot;,&quot;2627&quot;,&quot;2915&quot; : GetSpLevel = &quot; - RTM&quot;\nCase &quot;2514&quot;,&quot;3416&quot;,&quot;3506&quot;,&quot;3520&quot; : GetSpLevel = &quot; - SP1&quot;\nCase &quot;4128&quot;,&quot;4219&quot;,&quot;4330&quot;,&quot;5110&quot; : GetSpLevel = &quot; - SP2&quot;\nCase &quot;6308&quot;,&quot;6612&quot;,&quot;6626&quot; : GetSpLevel = &quot; - SP3&quot;\nCase Else\nEnd Select\nCase &quot;9&quot;\nSelect Case arrVersionstring(2)\nCase &quot;2720&quot; : GetSpLevel = &quot; - RTM&quot;\nCase &quot;3821&quot; : GetSpLevel = &quot; - SR1&quot;\nCase &quot;4527&quot; : GetSpLevel = &quot; - SP2&quot;\nCase &quot;9327&quot; : GetSpLevel = &quot; - SP3&quot;\nCase Else\nEnd Select\nCase Else\nEnd Select\n\nEnd Function\n&#039;=======================================================================================================\n\nSub ComputerProperties\nDim oOsItem\nDim arrVersion\nDim qOS\nOn Error Resume Next\n\nsComputerName = oWShell.ExpandEnvironmentStrings(&quot;%COMPUTERNAME%&quot;)\n\n&#039;OS info from WMI Win32_OperatingSystem\nSet qOS = oWmiLocal.ExecQuery(&quot;Select * from Win32_OperatingSystem&quot;)\nFor Each oOsItem in qOS \nsOSinfo = &quot;Operating System: &quot; &amp; oOsItem.Caption\nsOSinfo = sOSinfo &amp;&quot;,&quot;&amp; &quot;Service Pack: SP &quot; &amp; oOsItem.ServicePackMajorVersion\nsOSinfo = sOSinfo &amp;&quot;,&quot;&amp; &quot;Version: &quot; &amp; oOsItem.Version\nsOsVersion = oOsItem.Version\nsOSinfo = sOSinfo &amp;&quot;,&quot;&amp; &quot;Codepage: &quot; &amp; oOsItem.CodeSet\nsOSinfo = sOSinfo &amp;&quot;,&quot;&amp; &quot;Country Code: &quot; &amp; oOsItem.CountryCode\nsOSinfo = sOSinfo &amp;&quot;,&quot;&amp; &quot;Language: &quot; &amp; oOsItem.OSLanguage\nNext\n\n&#039;Build the VersionNT number\narrVersion = Split(sOsVersion,Delimiter(sOsVersion))\niVersionNt = CInt(arrVersion(0))*100 + CInt(arrVersion(1))\n\nEnd Sub &#039;ComputerProperties\n&#039;=======================================================================================================\n\n&#039;Sorts an array in descending order\nFunction BubbleSort(arrBubble)\n\nDim sTmp\n\nDim iCntOuter,iCntInner\n\nOn Error Resume Next\nBubbleSort = arrBubble\nIf NOT IsArray(arrBubble) Then Exit Function\n\nFor iCntOuter = UBound(arrBubble)-1 To 0 Step -1\n&#039;Inner sort loop\nFor iCntInner = 0 To iCntOuter\nIf arrBubble(iCntInner) &lt; arrBubble(iCntInner+1) Then\nsTmp = arrBubble(iCntInner+1)\narrBubble(iCntInner+1) = arrBubble(iCntInner)\narrBubble(iCntInner) = sTmp\nEnd If\nNext &#039;iCntInner\nNext &#039;iCntOuter\nBubbleSort = arrBubble\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Returns the delimiter of a number string\nFunction Delimiter (sVersion)\n\nDim iCnt, iAsc\n\nDelimiter = &quot; &quot;\nFor iCnt = 1 To Len(sVersion)\niAsc = Asc(Mid(sVersion, iCnt, 1))\nIf Not (iASC &gt;= 48 And iASC &lt;= 57) Then \nDelimiter = Mid(sVersion, iCnt, 1)\nExit Function\nEnd If\nNext &#039;iCnt\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Returns an array with valid folder locations.\n&#039;Local folders first (except WICacheDir), network folders, WICacheDir last\nFunction EnsureLocation(sLocations)\n\nDim sLocation,sLocalLocations,sNetworkLocations,DynLocation\n\nDim fLocal\n\nDim arrLocations\n\nOn Error Resume Next\nsLocalLocations = &quot;&quot; : sNetworkLocations = &quot;&quot;\nsLocations = sLocations &amp;&quot;;&quot;&amp;sScriptDir\nsLocations = Replace(sLocations,&quot;,&quot;,&quot;;&quot;)\narrLocations = RemoveDuplicates(Split(sLocations,&quot;;&quot;))\nsLocations = &quot;&quot;\nIf NOT fDynSUpdateDiscovered Then DiscoverDynSUpdateFolders\nFor Each Location in arrLocations\nsLocation=&quot;&quot;\nsLocation = Location\nIf oFso.FolderExists(sLocation) Then\n&#039;Ensure trailing &#039;\\&#039;\nIf NOT Right(sLocation,1)=&quot;\\&quot; Then sLocation=sLocation&amp;&quot;\\&quot;\nfLocal=dicLocalDisks.Exists(Left(sLocation,2))\nIf fLocal _\nThen sLocalLocations=sLocalLocations&amp;&quot;;&quot;&amp;sLocation _\nElse sNetworkLocations=sNetworkLocations&amp;&quot;;&quot;&amp;sLocation\nFor Each DynLocation in dicDynCultFolders.Keys\nIf oFso.FolderExists(sLocation &amp; DynLocation) Then\nIf NOT Right(DynLocation,1)=&quot;\\&quot; Then sLocation=sLocation&amp;&quot;\\&quot;\nIf fLocal _\nThen sLocalLocations=sLocalLocations&amp;&quot;;&quot;&amp;sLocation &amp; DynLocation _\nElse sNetworkLocations=sNetworkLocations&amp;&quot;;&quot;&amp;sLocation &amp; DynLocation\nEnd If\nNext &#039;DynLocation\nEnd If\nNext &#039;Location\nsLocations = sLocalLocations&amp;sNetworkLocations&amp;&quot;;&quot;&amp;sWICacheDir\nEnsureLocation=RemoveDuplicates(Split(Mid(sLocations,2),&quot;;&quot;))\n\nEnd Function &#039;EnsureLocation\n&#039;=======================================================================================================\n\n&#039;Remove duplicate entries from a one dimensional array\nFunction RemoveDuplicates(Array)\nDim Item\nDim oDic\n\nOn Error Resume Next\nSet oDic = CreateObject(&quot;Scripting.Dictionary&quot;)\nFor Each Item in Array\nIf Not oDic.Exists(Item) Then oDic.Add Item,Item\nNext &#039;Item\nRemoveDuplicates = oDic.Keys\nEnd Function &#039;RemoveDuplicates\n&#039;=======================================================================================================\n\n&#039;Converts the GUID \/ ProductCode into the compressed format\nFunction GetCompressedGuid (sGuid)\n\nDim sCompGUID\n\nDim i\n\nOn Error Resume Next\nsCompGUID = StrReverse(Mid(sGuid,2,8)) &amp; _\nStrReverse(Mid(sGuid,11,4)) &amp; _\nStrReverse(Mid(sGuid,16,4)) \nFor i = 21 To 24\nIf i Mod 2 Then\nsCompGUID = sCompGUID &amp; Mid(sGuid, (i + 1), 1)\nElse\nsCompGUID = sCompGUID &amp; Mid(sGuid, (i - 1), 1)\nEnd If\nNext\nFor i = 26 To 37\nIf i Mod 2 Then\nsCompGUID = sCompGUID &amp; Mid(sGuid, (i - 1), 1)\nElse\nsCompGUID = sCompGUID &amp; Mid(sGuid, (i + 1), 1)\nEnd If\nNext\nGetCompressedGuid = sCompGUID\n\nEnd Function\n&#039;=======================================================================================================\n\nFunction GetExpandedGuid (sGuid)\n\nDim sExpandGuid\nDim i\n\nOn Error Resume Next\nsExpandGuid = &quot;{&quot; &amp; StrReverse(Mid(sGuid,1,8)) &amp; &quot;-&quot; &amp; _\nStrReverse(Mid(sGuid,9,4)) &amp; &quot;-&quot; &amp; _\nStrReverse(Mid(sGuid,13,4))&amp; &quot;-&quot;\nFor i = 17 To 20\nIf i Mod 2 Then\nsExpandGuid = sExpandGuid &amp; mid(sGuid,(i + 1),1)\nElse\nsExpandGuid = sExpandGuid &amp; mid(sGuid,(i - 1),1)\nEnd If\nNext\nsExpandGuid = sExpandGuid &amp; &quot;-&quot;\nFor i = 21 To 32\nIf i Mod 2 Then\nsExpandGuid = sExpandGuid &amp; mid(sGuid,(i + 1),1)\nElse\nsExpandGuid = sExpandGuid &amp; mid(sGuid,(i - 1),1)\nEnd If\nNext\nsExpandGuid = sExpandGuid &amp; &quot;}&quot;\nGetExpandedGuid = sExpandGuid\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Translation for msiexec.exe error codes\nFunction MsiexecRetVal(iRetVal)\nOn Error Resume Next\nSelect Case iRetVal\nCase 0 : MsiexecRetVal = &quot;Success&quot;\nCase 1259 : MsiexecRetVal = &quot;APPHELP_BLOCK&quot;\nCase 1601 : MsiexecRetVal = &quot;INSTALL_SERVICE_FAILURE&quot;\nCase 1602 : MsiexecRetVal = &quot;INSTALL_USEREXIT&quot;\nCase 1603 : MsiexecRetVal = &quot;INSTALL_FAILURE&quot;\nCase 1604 : MsiexecRetVal = &quot;INSTALL_SUSPEND&quot;\nCase 1605 : MsiexecRetVal = &quot;UNKNOWN_PRODUCT&quot;\nCase 1606 : MsiexecRetVal = &quot;UNKNOWN_FEATURE&quot;\nCase 1607 : MsiexecRetVal = &quot;UNKNOWN_COMPONENT&quot;\nCase 1608 : MsiexecRetVal = &quot;UNKNOWN_PROPERTY&quot;\nCase 1609 : MsiexecRetVal = &quot;INVALID_HANDLE_STATE&quot;\nCase 1610 : MsiexecRetVal = &quot;BAD_CONFIGURATION&quot;\nCase 1611 : MsiexecRetVal = &quot;INDEX_ABSENT&quot;\nCase 1612 : MsiexecRetVal = &quot;INSTALL_SOURCE_ABSENT&quot;\nCase 1613 : MsiexecRetVal = &quot;INSTALL_PACKAGE_VERSION&quot;\nCase 1614 : MsiexecRetVal = &quot;PRODUCT_UNINSTALLED&quot;\nCase 1615 : MsiexecRetVal = &quot;BAD_QUERY_SYNTAX&quot;\nCase 1616 : MsiexecRetVal = &quot;INVALID_FIELD&quot;\nCase 1618 : MsiexecRetVal = &quot;INSTALL_ALREADY_RUNNING&quot;\nCase 1619 : MsiexecRetVal = &quot;INSTALL_PACKAGE_OPEN_FAILED&quot;\nCase 1620 : MsiexecRetVal = &quot;INSTALL_PACKAGE_INVALID&quot;\nCase 1621 : MsiexecRetVal = &quot;INSTALL_UI_FAILURE&quot;\nCase 1622 : MsiexecRetVal = &quot;INSTALL_LOG_FAILURE&quot;\nCase 1623 : MsiexecRetVal = &quot;INSTALL_LANGUAGE_UNSUPPORTED&quot;\nCase 1624 : MsiexecRetVal = &quot;INSTALL_TRANSFORM_FAILURE&quot;\nCase 1625 : MsiexecRetVal = &quot;INSTALL_PACKAGE_REJECTED&quot;\nCase 1626 : MsiexecRetVal = &quot;FUNCTION_NOT_CALLED&quot;\nCase 1627 : MsiexecRetVal = &quot;FUNCTION_FAILED&quot;\nCase 1628 : MsiexecRetVal = &quot;INVALID_TABLE&quot;\nCase 1629 : MsiexecRetVal = &quot;DATATYPE_MISMATCH&quot;\nCase 1630 : MsiexecRetVal = &quot;UNSUPPORTED_TYPE&quot;\nCase 1631 : MsiexecRetVal = &quot;CREATE_FAILED&quot;\nCase 1632 : MsiexecRetVal = &quot;INSTALL_TEMP_UNWRITABLE&quot;\nCase 1633 : MsiexecRetVal = &quot;INSTALL_PLATFORM_UNSUPPORTED&quot;\nCase 1634 : MsiexecRetVal = &quot;INSTALL_NOTUSED&quot;\nCase 1635 : MsiexecRetVal = &quot;PATCH_PACKAGE_OPEN_FAILED&quot;\nCase 1636 : MsiexecRetVal = &quot;PATCH_PACKAGE_INVALID&quot;\nCase 1637 : MsiexecRetVal = &quot;PATCH_PACKAGE_UNSUPPORTED&quot;\nCase 1638 : MsiexecRetVal = &quot;PRODUCT_VERSION&quot;\nCase 1639 : MsiexecRetVal = &quot;INVALID_COMMAND_LINE&quot;\nCase 1640 : MsiexecRetVal = &quot;INSTALL_REMOTE_DISALLOWED&quot;\nCase 1641 : MsiexecRetVal = &quot;SUCCESS_REBOOT_INITIATED&quot;\nCase 1642 : MsiexecRetVal = &quot;PATCH_TARGET_NOT_FOUND&quot;\nCase 1643 : MsiexecRetVal = &quot;PATCH_PACKAGE_REJECTED&quot;\nCase 1644 : MsiexecRetVal = &quot;INSTALL_TRANSFORM_REJECTED&quot;\nCase 1645 : MsiexecRetVal = &quot;INSTALL_REMOTE_PROHIBITED&quot;\nCase 1646 : MsiexecRetVal = &quot;PATCH_REMOVAL_UNSUPPORTED&quot;\nCase 1647 : MsiexecRetVal = &quot;UNKNOWN_PATCH&quot;\nCase 1648 : MsiexecRetVal = &quot;PATCH_NO_SEQUENCE&quot;\nCase 1649 : MsiexecRetVal = &quot;PATCH_REMOVAL_DISALLOWED&quot;\nCase 1650 : MsiexecRetVal = &quot;INVALID_PATCH_XML&quot;\nCase 3010 : MsiexecRetVal = &quot;SUCCESS_REBOOT_REQUIRED&quot;\nCase Else : MsiexecRetVal = &quot;Unknown Return Value&quot;\nEnd Select\nEnd Function &#039;MsiexecRetVal\n&#039;=======================================================================================================\n\n&#039;Error codes for 2007 Office update packages (aka Microsoft Self-Extractor)\nFunction ExtractorRetVal(iRetVal)\n\nOn Error Resume Next\nSelect Case iRetVal\nCase 0 : ExtractorRetVal = &quot;Success&quot;\nCase 17301 : ExtractorRetVal = &quot;Error: General Detection error&quot;\nCase 17302 : ExtractorRetVal = &quot;Error: Applying patch&quot;\nCase 17303 : ExtractorRetVal = &quot;Error: Extracting file&quot;\nCase 17021 : ExtractorRetVal = &quot;Error: Creating temp folder&quot;\nCase 17022 : ExtractorRetVal = &quot;Success: Reboot flag set&quot;\nCase 17023 : ExtractorRetVal = &quot;Error: User cancelled installation&quot;\nCase 17024 : ExtractorRetVal = &quot;Error: Creating folder failed&quot;\nCase 17025 : ExtractorRetVal = &quot;Patch already installed&quot;\nCase 17026 : ExtractorRetVal = &quot;Patch already installed to admin installation&quot;\nCase 17027 : ExtractorRetVal = &quot;Installation source requires full file update&quot;\nCase 17028 : ExtractorRetVal = &quot;No product installed for contained patch&quot;\nCase 17029 : ExtractorRetVal = &quot;Patch failed to install&quot;\nCase 17030 : ExtractorRetVal = &quot;Detection: Invalid CIF format&quot;\nCase 17031 : ExtractorRetVal = &quot;Detection: Invalid baseline&quot;\nCase 17034 : ExtractorRetVal = &quot;Error: Required patch does not apply to the machine&quot;\nCase Else : ExtractorRetVal = &quot;Unknown Return Value&quot;\nEnd Select\nEnd Function &#039;ExtractorRetVal\n&#039;=======================================================================================================\n\n&#039;Get Version Major from GUID\nFunction GetVersionMajor(sProductCode)\nDim iVersionMajor\nOn Error Resume Next\n\niVersionMajor = 0\nIf UCase(Right(sProductCode,28)) = OFFICE_2000 Then iVersionMajor = 9\nIf UCase(Right(sProductCode,28)) = ORK_2000 Then iVersionMajor = 9\nIf UCase(Right(sProductCode,28)) = PRJ_2000 Then iVersionMajor = 9\nIf UCase(Right(sProductCode,28)) = VIS_2002 Then iVersionMajor = 10\nIf UCase(Right(sProductCode,28)) = OFFICE_2002 Then iVersionMajor = 10\nIf UCase(Right(sProductCode,28)) = OFFICE_2003 Then iVersionMajor = 11\nIf UCase(Right(sProductCode,28)) = PPS_2007 Then iVersionMajor = 12\nIf UCase(Right(sProductCode,17)) = OFFICEID Then iVersionMajor = Mid(sProductCode,4,2)\n\nGetVersionMajor = iVersionMajor\nEnd Function\n&#039;=======================================================================================================\n\nFunction GetProductID (sProdId,sVersion)\n\nDim sReturn\n\nSelect Case sVersion\n\nCase &quot;2010&quot;\nSelect Case sProdId\n\nCase &quot;000F&quot; : sReturn = &quot;Office Mondo&quot;\nCase &quot;0010&quot; : sReturn = &quot;Web Folders&quot;\nCase &quot;0011&quot; : sReturn = &quot;Office Professional Plus&quot;\nCase &quot;0012&quot; : sReturn = &quot;Office Standard&quot;\nCase &quot;0013&quot; : sReturn = &quot;Office Basic&quot;\nCase &quot;0014&quot; : sReturn = &quot;Office Professional&quot;\nCase &quot;0015&quot; : sReturn = &quot;Access&quot;\nCase &quot;0016&quot; : sReturn = &quot;Excel&quot;\nCase &quot;0017&quot; : sReturn = &quot;SharePoint Designer&quot;\nCase &quot;0018&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;0019&quot; : sReturn = &quot;Publisher&quot;\nCase &quot;001A&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;001B&quot; : sReturn = &quot;Word&quot;\nCase &quot;001C&quot; : sReturn = &quot;Access Runtime&quot;\nCase &quot;001F&quot; : sReturn = &quot;Proof&quot;\nCase &quot;0020&quot; : sReturn = &quot;Office Compatibility Pack for Word, Excel, and PowerPoint 2007 File Formats&quot;\nCase &quot;0021&quot; : sReturn = &quot;Visual Studio Web Authoring Component (Office Visual Web Developer)&quot;\nCase &quot;0026&quot; : sReturn = &quot;Expression Web&quot;\nCase &quot;0029&quot; : sReturn = &quot;Excel&quot;\nCase &quot;002A&quot; : sReturn = &quot;Office 64-bit Components&quot;\nCase &quot;002B&quot; : sReturn = &quot;Word&quot;\nCase &quot;002C&quot; : sReturn = &quot;Proofing&quot;\nCase &quot;002E&quot; : sReturn = &quot;Office Ultimate&quot;\nCase &quot;002F&quot; : sReturn = &quot;Office Home and Student&quot;\nCase &quot;0028&quot; : sReturn = &quot;Office IME&quot;\nCase &quot;0030&quot; : sReturn = &quot;Office Enterprise&quot;\nCase &quot;0031&quot; : sReturn = &quot;Office Professional Hybrid&quot;\nCase &quot;0033&quot; : sReturn = &quot;Office Personal&quot;\nCase &quot;0035&quot; : sReturn = &quot;Office Professional Hybrid&quot;\nCase &quot;0037&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;003A&quot; : sReturn = &quot;Project Standard&quot;\nCase &quot;003B&quot; : sReturn = &quot;Project Professional&quot;\nCase &quot;003D&quot; : sReturn = &quot;Office Single Image&quot;\nCase &quot;003F&quot; : sReturn = &quot;Excel Viewer&quot;\nCase &quot;0043&quot; : sReturn = &quot;Office 32bit Components&quot;\nCase &quot;0044&quot; : sReturn = &quot;InfoPath&quot;\nCase &quot;0045&quot; : sReturn = &quot;Expression Web&quot;\nCase &quot;0048&quot; : sReturn = &quot;Outlook Hotmail Connector&quot;\nCase &quot;0049&quot; : sReturn = &quot;Office Academic&quot;\nCase &quot;004A&quot; : sReturn = &quot;2003 Web Components&quot;\nCase &quot;0051&quot; : sReturn = &quot;Visio Professional&quot;\nCase &quot;0052&quot; : sReturn = &quot;Visio Viewer&quot;\nCase &quot;0053&quot; : sReturn = &quot;Visio Standard&quot;\nCase &quot;0054&quot; : sReturn = &quot;Visio MUI&quot;\nCase &quot;0055&quot; : sReturn = &quot;Visio MUI&quot;\nCase &quot;0057&quot; : sReturn = &quot;Visio&quot;\nCase &quot;0061&quot; : sReturn = &quot;Click-to-Run&quot;\nCase &quot;0062&quot; : sReturn = &quot;Click-to-Run&quot;\nCase &quot;0066&quot; : sReturn = &quot;Click-to-Run&quot;\nCase &quot;006C&quot; : sReturn = &quot;Click-to-Run&quot;\nCase &quot;006D&quot; : sReturn = &quot;Click-to-Run&quot;\nCase &quot;006E&quot; : sReturn = &quot;Office Shared&quot;\nCase &quot;006F&quot; : sReturn = &quot;Office&quot;\nCase &quot;0074&quot; : sReturn = &quot;Office Starter&quot;\nCase &quot;007A&quot; : sReturn = &quot;Outlook Connector&quot;\nCase &quot;007C&quot; : sReturn = &quot;Outlook Social Connector Provider for FaceBook&quot;\nCase &quot;007D&quot; : sReturn = &quot;Outlook Social Connector Provider for Windows Live Messenger&quot;\nCase &quot;008A&quot; : sReturn = &quot;Office Recent Documents Gadget&quot;\nCase &quot;008B&quot; : sReturn = &quot;Office Small Business Basics&quot;\nCase &quot;00A1&quot; : sReturn = &quot;OneNote&quot;\nCase &quot;00A3&quot; : sReturn = &quot;OneNote Home Student&quot;\nCase &quot;00A4&quot; : sReturn = &quot;Office 2003 Web Components&quot;\nCase &quot;00A7&quot; : sReturn = &quot;Calendar Printing Assistant for Microsoft Office Outlook&quot;\nCase &quot;00A9&quot; : sReturn = &quot;InterConnect&quot;\nCase &quot;00AF&quot; : sReturn = &quot;PowerPoint Viewer&quot;\nCase &quot;00B0&quot; : sReturn = &quot;Save as PDF Add-in&quot;\nCase &quot;00B1&quot; : sReturn = &quot;Save as XPS Add-in&quot;\nCase &quot;00B2&quot; : sReturn = &quot;Save as PDF or XPS Add-in&quot;\nCase &quot;00B3&quot; : sReturn = &quot;Project Add-in for Outlook&quot;\nCase &quot;00B4&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;00B5&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;00B9&quot; : sReturn = &quot;Application Error Reporting&quot;\nCase &quot;00BA&quot; : sReturn = &quot;Groove&quot;\nCase &quot;00BC&quot; : sReturn = &quot;InterConnect Outlook&quot;\nCase &quot;00CA&quot; : sReturn = &quot;Office Small Business&quot;\nCase &quot;00E0&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;00D1&quot; : sReturn = &quot;Access Connectivity Engine ACE&quot;\nCase &quot;0100&quot; : sReturn = &quot;Office MUI&quot;\nCase &quot;0101&quot; : sReturn = &quot;Office XMUI&quot;\nCase &quot;0103&quot; : sReturn = &quot;Office Proofing Tools Kit&quot;\nCase &quot;0114&quot; : sReturn = &quot;Groove Setup Metadata&quot;\nCase &quot;0115&quot; : sReturn = &quot;Office Shared Setup Metadata&quot;\nCase &quot;0116&quot; : sReturn = &quot;Office Shared Setup Metadata&quot;\nCase &quot;0117&quot; : sReturn = &quot;Access Setup Metadata&quot;\nCase &quot;011A&quot; : sReturn = &quot;Send A Smile&quot;\nCase &quot;011D&quot; : sReturn = &quot;Office Professional Plus Subscription&quot;\nCase &quot;011F&quot; : sReturn = &quot;Outlook Connector&quot;\n\nCase &quot;1014&quot; : sReturn = &quot;SharePoint Foundation Core&quot;\nCase &quot;1015&quot; : sReturn = &quot;SharePoint Foundation Lang Pack&quot;\nCase &quot;101F&quot; : sReturn = &quot;Office Server Proof&quot;\nCase &quot;1031&quot; : sReturn = &quot;Project Server Web Front End&quot;\nCase &quot;1032&quot; : sReturn = &quot;Project Server Application Server&quot;\nCase &quot;104B&quot; : sReturn = &quot;Office SharePoint Portal&quot;\nCase &quot;104C&quot; : sReturn = &quot;User Profiles&quot;\nCase &quot;104E&quot; : sReturn = &quot;Office SharePoint Portal Language Pack&quot;\nCase &quot;107F&quot; : sReturn = &quot;Shared Components&quot;\nCase &quot;1080&quot; : sReturn = &quot;Office Shared Coms (Srv) Language Pack&quot;\nCase &quot;1088&quot; : sReturn = &quot;Slide Library&quot;\nCase &quot;10B0&quot; : sReturn = &quot;Project Server Language Pack&quot;\nCase &quot;10D7&quot; : sReturn = &quot;InfoPath Forms Services&quot;\nCase &quot;10D8&quot; : sReturn = &quot;InfoPath Forms Services Language Pack&quot;\nCase &quot;10EB&quot; : sReturn = &quot;Office Document Lifecycle Application Server Components&quot;\nCase &quot;10EC&quot; : sReturn = &quot;Word Server&quot;\nCase &quot;10ED&quot; : sReturn = &quot;Word Server Language Pack&quot;\nCase &quot;10EE&quot; : sReturn = &quot;PerformancePoint Services&quot;\nCase &quot;10F0&quot; : sReturn = &quot;PerformancePoint Services Language Pack&quot;\nCase &quot;10F1&quot; : sReturn = &quot;Visio Services Language Pack&quot;\nCase &quot;10F3&quot; : sReturn = &quot;Visio Services Web Front End Components&quot;\nCase &quot;10F5&quot; : sReturn = &quot;Excel Services&quot;\nCase &quot;10F6&quot; : sReturn = &quot;Excel Services Web Front End Components&quot;\nCase &quot;10F7&quot; : sReturn = &quot;Document Lifecycle Components&quot;\nCase &quot;10F8&quot; : sReturn = &quot;Excel Services Language Pack&quot;\nCase &quot;10FB&quot; : sReturn = &quot;Search Server&quot;\nCase &quot;10FC&quot; : sReturn = &quot;Search&quot;\nCase &quot;10FD&quot; : sReturn = &quot;Search Server Language Pack&quot;\nCase &quot;1103&quot; : sReturn = &quot;Office Document Lifecycle Components Language Pack&quot;\nCase &quot;1104&quot; : sReturn = &quot;Office Slide Library Language Pack&quot;\nCase &quot;1105&quot; : sReturn = &quot;Office Primary Interop Assemblies&quot;\nCase &quot;110D&quot; : sReturn = &quot;SharePoint Server&quot;\nCase &quot;110F&quot; : sReturn = &quot;Project Server&quot;\nCase &quot;1110&quot; : sReturn = &quot;Windows SharePoint Services (WSS)&quot;\nCase &quot;1121&quot; : sReturn = &quot;Office SharePoint Server SDK and ECM Starter Kit&quot;\nCase &quot;1122&quot; : sReturn = &quot;Windows SharePoint Services Developer Resources&quot;\nCase &quot;1123&quot; : sReturn = &quot;Access Services Server&quot;\nCase &quot;1124&quot; : sReturn = &quot;Access Services Language Pack&quot;\nCase &quot;1125&quot; : sReturn = &quot;Web Companion Web Front End Components&quot;\nCase &quot;1127&quot; : sReturn = &quot;Web Companion Components Language Pack&quot;\nCase &quot;112A&quot; : sReturn = &quot;Web Analytics Web Front End Components&quot;\nCase &quot;112D&quot; : sReturn = &quot;Office Web Apps&quot;\nCase &quot;1131&quot; : sReturn = &quot;Web Analytics Language Pack&quot;\nCase &quot;1138&quot; : sReturn = &quot;Excel Mobile Viewer Components&quot;\nCase &quot;2000&quot; : sReturn = &quot;Microsoft Filter Pack&quot;\nCase Else\n\nEnd Select &#039;sProdId\n\nCase &quot;2007&quot;\nSelect Case sProdId\n\nCase &quot;0010&quot; : sReturn = &quot;Web Folders&quot;\nCase &quot;0011&quot; : sReturn = &quot;Office Professional Plus&quot;\nCase &quot;0012&quot; : sReturn = &quot;Office Standard&quot;\nCase &quot;0013&quot; : sReturn = &quot;Office Basic&quot;\nCase &quot;0014&quot; : sReturn = &quot;Office Professional&quot;\nCase &quot;0015&quot; : sReturn = &quot;Access&quot;\nCase &quot;0016&quot; : sReturn = &quot;Excel&quot;\nCase &quot;0017&quot; : sReturn = &quot;SharePoint Designer&quot;\nCase &quot;0018&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;0019&quot; : sReturn = &quot;Publisher&quot;\nCase &quot;001A&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;001B&quot; : sReturn = &quot;Word&quot;\nCase &quot;001C&quot; : sReturn = &quot;Access Runtime&quot;\nCase &quot;001F&quot; : sReturn = &quot;Proof&quot;\nCase &quot;0020&quot; : sReturn = &quot;Office Compatibility Pack for Word, Excel, and PowerPoint 2007 File Formats&quot;\nCase &quot;0021&quot; : sReturn = &quot;Visual Studio Web Authoring Component (Office Visual Web Developer 2007)&quot;\nCase &quot;0026&quot; : sReturn = &quot;Expression Web&quot;\nCase &quot;0029&quot; : sReturn = &quot;Excel&quot;\nCase &quot;002A&quot; : sReturn = &quot;Office 64-bit Components&quot;\nCase &quot;002B&quot; : sReturn = &quot;Word&quot;\nCase &quot;002C&quot; : sReturn = &quot;Proofing&quot;\nCase &quot;002E&quot; : sReturn = &quot;Office Ultimate&quot;\nCase &quot;002F&quot; : sReturn = &quot;Office Home and Student&quot;\nCase &quot;0028&quot; : sReturn = &quot;Office IME&quot;\nCase &quot;0030&quot; : sReturn = &quot;Office Enterprise&quot;\nCase &quot;0031&quot; : sReturn = &quot;Office Professional Hybrid&quot;\nCase &quot;0033&quot; : sReturn = &quot;Office Personal&quot;\nCase &quot;0035&quot; : sReturn = &quot;Office Professional Hybrid 2007&quot;\nCase &quot;0038&quot; : sReturn = &quot;Time Zone Data Update Tool for Outlook&quot;\nCase &quot;0037&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;003A&quot; : sReturn = &quot;Project Standard&quot;\nCase &quot;003B&quot; : sReturn = &quot;Project Professional&quot;\nCase &quot;003F&quot; : sReturn = &quot;Excel Viewer&quot;\nCase &quot;0043&quot; : sReturn = &quot;Time Zone Data Update Engine for Outlook&quot;\nCase &quot;0044&quot; : sReturn = &quot;InfoPath&quot;\nCase &quot;0045&quot; : sReturn = &quot;Expression Web 2&quot;\nCase &quot;0051&quot; : sReturn = &quot;Visio Professional&quot;\nCase &quot;0052&quot; : sReturn = &quot;Visio Viewer&quot;\nCase &quot;0053&quot; : sReturn = &quot;Visio Standard&quot;\nCase &quot;0054&quot; : sReturn = &quot;Visio MUI&quot;\nCase &quot;0055&quot; : sReturn = &quot;Visio MUI&quot;\nCase &quot;0057&quot; : sReturn = &quot;Visio&quot;\nCase &quot;006E&quot; : sReturn = &quot;Office Shared&quot;\nCase &quot;008A&quot; : sReturn = &quot;Office Recent Documents Gadget&quot;\nCase &quot;00A1&quot; : sReturn = &quot;OneNote&quot;\nCase &quot;00A3&quot; : sReturn = &quot;OneNote Home Student&quot;\nCase &quot;00A4&quot; : sReturn = &quot;Office 2003 Web Components&quot;\nCase &quot;00A7&quot; : sReturn = &quot;Calendar Printing Assistant for Microsoft Office Outlook&quot;\nCase &quot;00A9&quot; : sReturn = &quot;InterConnect&quot;\nCase &quot;00AF&quot; : sReturn = &quot;PowerPoint Viewer&quot;\nCase &quot;00B0&quot; : sReturn = &quot;Save as PDF Add-in for 2007 Microsoft Office programs&quot;\nCase &quot;00B1&quot; : sReturn = &quot;Save as XPS Add-in for 2007 Microsoft Office programs&quot;\nCase &quot;00B2&quot; : sReturn = &quot;Save as PDF or XPS Add-in for 2007 Microsoft Office programs&quot;\nCase &quot;00B3&quot; : sReturn = &quot;Project Add-in for Outlook&quot;\nCase &quot;00B4&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;00B5&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;00B9&quot; : sReturn = &quot;Application Error Reporting&quot;\nCase &quot;00BA&quot; : sReturn = &quot;Groove&quot;\nCase &quot;00BC&quot; : sReturn = &quot;InterConnect Outlook&quot;\nCase &quot;00CA&quot; : sReturn = &quot;Office Small Business&quot;\nCase &quot;00E0&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;00D1&quot; : sReturn = &quot;Access Connectivity Engine ACE&quot;\nCase &quot;0100&quot; : sReturn = &quot;Office MUI&quot;\nCase &quot;0101&quot; : sReturn = &quot;Office XMUI&quot;\nCase &quot;0103&quot; : sReturn = &quot;Office Proofing Tools Kit&quot;\nCase &quot;0114&quot; : sReturn = &quot;Groove Setup Metadata&quot;\nCase &quot;0115&quot; : sReturn = &quot;Office Shared Setup Metadata&quot;\nCase &quot;0116&quot; : sReturn = &quot;Office Shared Setup Metadata&quot;\nCase &quot;0117&quot; : sReturn = &quot;Access Setup Metadata&quot;\nCase &quot;011A&quot; : sReturn = &quot;Windows Live Web Folder Connector&quot;\nCase &quot;011F&quot; : sReturn = &quot;Outlook Connector&quot;\nCase &quot;1014&quot; : sReturn = &quot;Windows SharePoint Services 3.0 (STS)&quot;\nCase &quot;1015&quot; : sReturn = &quot;Windows SharePoint Services 3.0 Lang Pack&quot;\nCase &quot;1032&quot; : sReturn = &quot;Project Server Application Server&quot;\nCase &quot;104B&quot; : sReturn = &quot;Office SharePoint Portal&quot;\nCase &quot;104E&quot; : sReturn = &quot;Office SharePoint Portal Language Pack&quot;\nCase &quot;107F&quot; : sReturn = &quot;Office Shared Components (Srv)&quot;\nCase &quot;1080&quot; : sReturn = &quot;Office Shared Coms (Srv)&quot;\nCase &quot;1088&quot; : sReturn = &quot;Office Slide Library&quot;\nCase &quot;10D7&quot; : sReturn = &quot;InfoPath Forms Services&quot;\nCase &quot;10D8&quot; : sReturn = &quot;InfoPath Forms Services Language Pack&quot;\nCase &quot;10EB&quot; : sReturn = &quot;Office Document Lifecycle Application Server Components&quot;\nCase &quot;10F5&quot; : sReturn = &quot;Excel Services&quot;\nCase &quot;10F6&quot; : sReturn = &quot;Excel Services Web Front End Components&quot;\nCase &quot;10F7&quot; : sReturn = &quot;Office Document Lifecycle Components&quot;\nCase &quot;10F8&quot; : sReturn = &quot;Excel Services Language Pack&quot;\nCase &quot;10FB&quot; : sReturn = &quot;Search Front End&quot;\nCase &quot;10FC&quot; : sReturn = &quot;Search&quot;\nCase &quot;10FD&quot; : sReturn = &quot;Search Language Pack&quot;\nCase &quot;1103&quot; : sReturn = &quot;Office Document Lifecycle Components Language Pack&quot;\nCase &quot;1104&quot; : sReturn = &quot;Office Slide Library Language Pack&quot;\nCase &quot;1105&quot; : sReturn = &quot;Office Primary Interop Assemblies&quot;\nCase &quot;1106&quot; : sReturn = &quot;Groove Management Server&quot;\nCase &quot;1109&quot; : sReturn = &quot;Groove Server Relay&quot;\nCase &quot;110D&quot; : sReturn = &quot;Office SharePoint Server (MOSS)&quot;\nCase &quot;110F&quot; : sReturn = &quot;Project Server&quot;\nCase &quot;1110&quot; : sReturn = &quot;Windows SharePoint Services 3.0 (WSS)&quot;\nCase &quot;1121&quot; : sReturn = &quot;Office SharePoint Server 2007 SDK and ECM Starter Kit&quot;\nCase &quot;1122&quot; : sReturn = &quot;Windows SharePoint Services Developer Resources 1.2&quot;\nCase Else\n\nEnd Select &#039;sProdId\n\nCase &quot;2003&quot;\nSelect Case sProdId\n\nCase &quot;11&quot; : sReturn = &quot;Office Professional Enterprise&quot;\nCase &quot;12&quot; : sReturn = &quot;Office Standard&quot;\nCase &quot;13&quot; : sReturn = &quot;Office Basic&quot;\nCase &quot;14&quot; : sReturn = &quot;Windows SharePoint Services 2.0&quot;\nCase &quot;15&quot; : sReturn = &quot;Access&quot;\nCase &quot;16&quot; : sReturn = &quot;Excel&quot;\nCase &quot;17&quot; : sReturn = &quot;FrontPage&quot;\nCase &quot;18&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;19&quot; : sReturn = &quot;Publisher&quot;\nCase &quot;1A&quot; : sReturn = &quot;Outlook Professional&quot;\nCase &quot;1B&quot; : sReturn = &quot;Word&quot;\nCase &quot;1C&quot; : sReturn = &quot;Access Runtime&quot;\nCase &quot;1E&quot; : sReturn = &quot;Office MUI&quot;\nCase &quot;1F&quot; : sReturn = &quot;Office Proofing Tools Kit&quot;\nCase &quot;23&quot; : sReturn = &quot;Office MUI&quot;\nCase &quot;24&quot; : sReturn = &quot;Office Resource Kit (ORK)&quot;\nCase &quot;26&quot; : sReturn = &quot;Office XP Web Components&quot;\nCase &quot;2E&quot; : sReturn = &quot;Office Research Service SDK&quot;\nCase &quot;32&quot; : sReturn = &quot;Project Server&quot;\nCase &quot;33&quot; : sReturn = &quot;Office Personal Edition&quot;\nCase &quot;3A&quot; : sReturn = &quot;Project Standard&quot; \nCase &quot;3B&quot; : sReturn = &quot;Project Professional&quot;\nCase &quot;3C&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;44&quot; : sReturn = &quot;InfoPath&quot;\nCase &quot;48&quot; : sReturn = &quot;InfoPath 2003 Toolkit for Visual Studio 2005&quot;\nCase &quot;49&quot; : sReturn = &quot;Office Primary Interop Assemblies&quot;\nCase &quot;51&quot; : sReturn = &quot;Visio Professional&quot;\nCase &quot;52&quot; : sReturn = &quot;Visio Viewer&quot;\nCase &quot;53&quot; : sReturn = &quot;Visio Standard&quot;\nCase &quot;55&quot; : sReturn = &quot;Visio for Enterprise Architects&quot;\nCase &quot;5E&quot; : sReturn = &quot;Visio MUI&quot;\nCase &quot;83&quot; : sReturn = &quot;Office HTML Viewer&quot;\nCase &quot;84&quot; : sReturn = &quot;Excel Viewer&quot;\nCase &quot;85&quot; : sReturn = &quot;Word Viewer&quot;\nCase &quot;92&quot; : sReturn = &quot;Windows SharePoint Services 2.0 English Template Pack&quot;\nCase &quot;93&quot; : sReturn = &quot;Office Web Parts and Components&quot;\nCase &quot;A1&quot; : sReturn = &quot;OneNote&quot;\nCase &quot;A4&quot; : sReturn = &quot;Office Web Components&quot;\nCase &quot;A5&quot; : sReturn = &quot;SharePoint Migration Tool&quot;\nCase &quot;A9&quot; : sReturn = &quot;InterConnect 2004&quot;\nCase &quot;AA&quot; : sReturn = &quot;PowerPoint 2003 Presentation Broadcast&quot;\nCase &quot;AB&quot; : sReturn = &quot;PowerPoint 2003 Template Pack 1&quot;\nCase &quot;AC&quot; : sReturn = &quot;PowerPoint 2003 Template Pack 2&quot;\nCase &quot;AD&quot; : sReturn = &quot;PowerPoint 2003 Template Pack 3&quot;\nCase &quot;AE&quot; : sReturn = &quot;Office Organization Chart 2.0&quot;\nCase &quot;CA&quot; : sReturn = &quot;Office Small Business Edition&quot;\nCase &quot;D0&quot; : sReturn = &quot;Access Developer Extensions&quot;\nCase &quot;DC&quot; : sReturn = &quot;Office Smart Document SDK&quot;\nCase &quot;E0&quot; : sReturn = &quot;Outlook Standard&quot;\nCase &quot;E3&quot; : sReturn = &quot;Office Professional Edition (with InfoPath)&quot;\nCase &quot;F7&quot; : sReturn = &quot;InfoPath 2003 Toolkit for Visual Studio .NET&quot;\nCase &quot;F8&quot; : sReturn = &quot;Office Remove Hidden Data Tool&quot;\nCase &quot;FD&quot; : sReturn = &quot;Outlook (distributed by MSN)&quot;\nCase &quot;FF&quot; : sReturn = &quot;Office Language Interface Pack&quot;\nCase Else : sReturn = &quot;&quot;\n\nEnd Select &#039;ProdId\n\nCase &quot;2002&quot;\nSelect Case sProdId\n\nCase &quot;11&quot; : sReturn = &quot;Office Professional&quot;\nCase &quot;12&quot; : sReturn = &quot;Office Standard&quot;\nCase &quot;13&quot; : sReturn = &quot;Office Small Business&quot;\nCase &quot;14&quot; : sReturn = &quot;Office Web Server&quot;\nCase &quot;15&quot; : sReturn = &quot;Access&quot;\nCase &quot;16&quot; : sReturn = &quot;Excel&quot;\nCase &quot;17&quot; : sReturn = &quot;FrontPage&quot;\nCase &quot;18&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;19&quot; : sReturn = &quot;Publisher&quot;\nCase &quot;1A&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;1B&quot; : sReturn = &quot;Word&quot;\nCase &quot;1C&quot; : sReturn = &quot;Access Runtime&quot;\nCase &quot;1D&quot; : sReturn = &quot;Frontpage MUI&quot;\nCase &quot;1E&quot; : sReturn = &quot;Office MUI&quot;\nCase &quot;1F&quot; : sReturn = &quot;Office Proofing Tools Kit&quot;\nCase &quot;20&quot; : sReturn = &quot;System Files Update&quot;\nCase &quot;23&quot; : sReturn = &quot;Office MUI Wizard&quot;\nCase &quot;24&quot; : sReturn = &quot;Office Resource Kit (ORK)&quot;\nCase &quot;25&quot; : sReturn = &quot;Office Resource Kit (ORK) Web Download&quot;\nCase &quot;26&quot; : sReturn = &quot;Office XP Web Components&quot;\nCase &quot;27&quot; : sReturn = &quot;Project&quot;\nCase &quot;28&quot; : sReturn = &quot;Office Professional with FrontPage&quot;\nCase &quot;29&quot; : sReturn = &quot;Office Professional Subscription&quot;\nCase &quot;2A&quot; : sReturn = &quot;Office Small Business Subscription&quot;\nCase &quot;2B&quot; : sReturn = &quot;Publisher Deluxe Edition&quot;\nCase &quot;2F&quot; : sReturn = &quot;Office Standalone IME&quot;\nCase &quot;30&quot; : sReturn = &quot;Office Media Content &quot;\nCase &quot;32&quot; : sReturn = &quot;Project Web Server&quot;\nCase &quot;33&quot; : sReturn = &quot;Office PIPC1 - Pre Installed PC&quot;\nCase &quot;34&quot; : sReturn = &quot;Office PIPC2 - Pre Installed PC&quot;\nCase &quot;35&quot; : sReturn = &quot;Office Media Content Deluxe&quot;\nCase &quot;3A&quot; : sReturn = &quot;Project Standard&quot; \nCase &quot;3B&quot; : sReturn = &quot;Project Professional&quot;\nCase &quot;3C&quot; : sReturn = &quot;Project MUI&quot;\nCase &quot;3D&quot; : sReturn = &quot;Office Standard for Students and Teachers&quot;\nCase &quot;51&quot; : sReturn = &quot;Visio Professional&quot;\nCase &quot;52&quot; : sReturn = &quot;Visio Viewer&quot;\nCase &quot;53&quot; : sReturn = &quot;Visio Standard&quot;\nCase &quot;54&quot; : sReturn = &quot;Visio Standard&quot;\nCase &quot;91&quot; : sReturn = &quot;Office Professional&quot;\nCase &quot;92&quot; : sReturn = &quot;Office Standard&quot;\nCase &quot;93&quot; : sReturn = &quot;Office Small Business&quot;\nCase &quot;94&quot; : sReturn = &quot;Office Web Server&quot;\nCase &quot;95&quot; : sReturn = &quot;Access&quot;\nCase &quot;96&quot; : sReturn = &quot;Excel&quot;\nCase &quot;97&quot; : sReturn = &quot;FrontPage&quot;\nCase &quot;98&quot; : sReturn = &quot;PowerPoint&quot;\nCase &quot;99&quot; : sReturn = &quot;Publisher&quot;\nCase &quot;9A&quot; : sReturn = &quot;Outlook&quot;\nCase &quot;9B&quot; : sReturn = &quot;Word&quot;\nCase &quot;9C&quot; : sReturn = &quot;Access Runtime&quot;\nCase Else : sReturn = &quot;&quot;\n\nEnd Select &#039;ProdId\n\nCase &quot;2000&quot;\nSelect Case CInt(&quot;&amp;h&quot;&amp;sProdId)\n\nCase 0 : sReturn = &quot;Office Premium CD1&quot;\nCase 1 : sReturn = &quot;Office Professional&quot;\nCase 2 : sReturn = &quot;Office Standard&quot;\nCase 3 : sReturn = &quot;Office Small Business&quot;\nCase 4 : sReturn = &quot;Office Premium CD2&quot;\nCase 5 : sReturn = &quot;Office CD2 SMALL&quot;\nCase 6 : sReturn = &quot;Office Personal&quot;\nCase 7 : sReturn = &quot;Word and Excel&quot;\nCase 16 : sReturn = &quot;Access&quot;\nCase 17 : sReturn = &quot;Excel&quot;\nCase 18 : sReturn = &quot;FrontPage&quot;\nCase 19 : sReturn = &quot;PowerPoint&quot;\nCase 20 : sReturn = &quot;Publisher&quot;\nCase 21 : sReturn = &quot;Office Server Extensions&quot;\nCase 22 : sReturn = &quot;Outlook&quot;\nCase 23 : sReturn = &quot;Word&quot;\nCase 24 : sReturn = &quot;Access Runtime&quot;\nCase 25 : sReturn = &quot;FrontPage Server Extensions&quot;\nCase 26 : sReturn = &quot;Publisher Standalone OEM&quot;\nCase 27 : sReturn = &quot;DMMWeb&quot;\nCase 28 : sReturn = &quot;FP WECCOM&quot;\nCase 29 : sReturn = &quot;Word&quot;\nCase 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47 : sReturn = &quot;Office MUI&quot;\nCase 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 : sReturn = &quot;Office Proofing Tools Kit&quot;\nCase 64 : sReturn = &quot;Publisher Trial&quot;\nCase 65 : sReturn = &quot;Publisher Trial Web&quot;\nCase 66 : sReturn = &quot;SBB&quot;\nCase 67 : sReturn = &quot;SBT&quot;\nCase 68 : sReturn = &quot;SBT CD2&quot;\nCase 69 : sReturn = &quot;SBTART&quot;\nCase 70 : sReturn = &quot;Office Web Components&quot;\nCase 71 : sReturn = &quot;VP Office CD2 with LVP&quot;\nCase 72 : sReturn = &quot;VP PUB with LVP&quot;\nCase 73 : sReturn = &quot;VP PUB with LVP OEM&quot;\nCase 79 : sReturn = &quot;Access 2000 SR-1 Run-Time Minimum&quot;\nCase Else : sReturn = &quot;&quot;\n\nEnd Select &#039;sProdId\n\nCase Else : sReturn = &quot;&quot;\nEnd Select &#039;sVersion\n\nGetProductID = sReturn\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;Gets the release type ID from the ProductCode as integer and returns a string\nFunction GetReleaseType (iR)\n\nDim sR\n\nSelect Case iR\n&#039;Disable Case 0 to avoid noise in the output\nCase 0 : sR = &quot;Volume License&quot;\nCase 1 : sR = &quot;Retail&quot;\nCase 2 : sR = &quot;Trial&quot;\nCase Else : sR = &quot;&quot;\nEnd Select\n\nGetReleaseType = sR\n\nEnd Function\n&#039;=======================================================================================================\n\n&#039;=======================================================================================================\n\n&#039;Get the culture info tag from LCID\nFunction GetCultureInfo (sLcid)\n\nDim sLang\n\nSelect Case UCase(Hex(CInt(sLcid)))\nCase &quot;0&quot; : sLang = &quot;neutral&quot;\nCase &quot;7F&quot; : sLang = &quot;invariant&quot; &#039;Invariant culture\nCase &quot;36&quot; : sLang = &quot;af&quot; &#039; Afrikaans\nCase &quot;436&quot; : sLang = &quot;af-ZA&quot; &#039; Afrikaans (South Africa)\nCase &quot;1C&quot; : sLang = &quot;sq&quot; &#039; Albanian\nCase &quot;41C&quot; : sLang = &quot;sq-AL&quot; &#039; Albanian (Albania)\nCase &quot;1&quot; : sLang = &quot;ar&quot; &#039; Arabic\nCase &quot;1401&quot; : sLang = &quot;ar-DZ&quot; &#039; Arabic (Algeria)\nCase &quot;3C01&quot; : sLang = &quot;ar-BH&quot; &#039; Arabic (Bahrain)\nCase &quot;C01&quot; : sLang = &quot;ar-EG&quot; &#039; Arabic (Egypt)\nCase &quot;801&quot; : sLang = &quot;ar-IQ&quot; &#039; Arabic (Iraq)\nCase &quot;2C01&quot; : sLang = &quot;ar-JO&quot; &#039; Arabic (Jordan)\nCase &quot;3401&quot; : sLang = &quot;ar-KW&quot; &#039; Arabic (Kuwait)\nCase &quot;3001&quot; : sLang = &quot;ar-LB&quot; &#039; Arabic (Lebanon)\nCase &quot;1001&quot; : sLang = &quot;ar-LY&quot; &#039; Arabic (Libya)\nCase &quot;1801&quot; : sLang = &quot;ar-MA&quot; &#039; Arabic (Morocco)\nCase &quot;2001&quot; : sLang = &quot;ar-OM&quot; &#039; Arabic (Oman)\nCase &quot;4001&quot; : sLang = &quot;ar-QA&quot; &#039; Arabic (Qatar)\nCase &quot;401&quot; : sLang = &quot;ar-SA&quot; &#039; Arabic (Saudi Arabia)\nCase &quot;2801&quot; : sLang = &quot;ar-SY&quot; &#039; Arabic (Syria)\nCase &quot;1C01&quot; : sLang = &quot;ar-TN&quot; &#039; Arabic (Tunisia)\nCase &quot;3801&quot; : sLang = &quot;ar-AE&quot; &#039; Arabic (U.A.E.)\nCase &quot;2401&quot; : sLang = &quot;ar-YE&quot; &#039; Arabic (Yemen)\nCase &quot;2B&quot; : sLang = &quot;hy&quot; &#039; Armenian\nCase &quot;42B&quot; : sLang = &quot;hy-AM&quot; &#039; Armenian (Armenia)\nCase &quot;2C&quot; : sLang = &quot;az&quot; &#039; Azeri\nCase &quot;82C&quot; : sLang = &quot;az-Cyrl-AZ&quot; &#039; Azeri (Azerbaijan, Cyrillic)\nCase &quot;42C&quot; : sLang = &quot;az-Latn-AZ&quot; &#039; Azeri (Azerbaijan, Latin)\nCase &quot;2D&quot; : sLang = &quot;eu&quot; &#039; Basque\nCase &quot;42D&quot; : sLang = &quot;eu-ES&quot; &#039; Basque (Basque)\nCase &quot;23&quot; : sLang = &quot;be&quot; &#039; Belarusian\nCase &quot;423&quot; : sLang = &quot;be-BY&quot; &#039; Belarusian (Belarus)\nCase &quot;2&quot; : sLang = &quot;bg&quot; &#039; Bulgarian\nCase &quot;402&quot; : sLang = &quot;bg-BG&quot; &#039; Bulgarian (Bulgaria)\nCase &quot;3&quot; : sLang = &quot;ca&quot; &#039; Catalan\nCase &quot;403&quot; : sLang = &quot;ca-ES&quot; &#039; Catalan (Catalan)\nCase &quot;C04&quot; : sLang = &quot;zh-HK&quot; &#039; Chinese (Hong Kong SAR, PRC)\nCase &quot;1404&quot; : sLang = &quot;zh-MO&quot; &#039; Chinese (Macao SAR)\nCase &quot;804&quot; : sLang = &quot;zh-CN&quot; &#039; Chinese (PRC)\nCase &quot;4&quot; : sLang = &quot;zh-Hans&quot; &#039; Chinese (Simplified)\nCase &quot;1004&quot; : sLang = &quot;zh-SG&quot; &#039; Chinese (Singapore)\nCase &quot;404&quot; : sLang = &quot;zh-TW&quot; &#039; Chinese (Taiwan)\nCase &quot;7C04&quot; : sLang = &quot;zh-Hant&quot; &#039; Chinese (Traditional)\nCase &quot;1A&quot; : sLang = &quot;hr&quot; &#039; Croatian\nCase &quot;41A&quot; : sLang = &quot;hr-HR&quot; &#039; Croatian (Croatia)\nCase &quot;5&quot; : sLang = &quot;cs&quot; &#039; Czech\nCase &quot;405&quot; : sLang = &quot;cs-CZ&quot; &#039; Czech (Czech Republic)\nCase &quot;6&quot; : sLang = &quot;da&quot; &#039; Danish\nCase &quot;406&quot; : sLang = &quot;da-DK&quot; &#039; Danish (Denmark)\nCase &quot;65&quot; : sLang = &quot;dv&quot; &#039; Divehi\nCase &quot;465&quot; : sLang = &quot;dv-MV&quot; &#039; Divehi (Maldives)\nCase &quot;13&quot; : sLang = &quot;nl&quot; &#039; Dutch\nCase &quot;813&quot; : sLang = &quot;nl-BE&quot; &#039; Dutch (Belgium)\nCase &quot;413&quot; : sLang = &quot;nl-NL&quot; &#039; Dutch (Netherlands)\nCase &quot;9&quot; : sLang = &quot;en&quot; &#039; English\nCase &quot;C09&quot; : sLang = &quot;en-AU&quot; &#039; English (Australia)\nCase &quot;2809&quot; : sLang = &quot;en-BZ&quot; &#039; English (Belize)\nCase &quot;1009&quot; : sLang = &quot;en-CA&quot; &#039; English (Canada)\nCase &quot;2409&quot; : sLang = &quot;en-029&quot; &#039; English (Caribbean)\nCase &quot;1809&quot; : sLang = &quot;en-IE&quot; &#039; English (Ireland)\nCase &quot;2009&quot; : sLang = &quot;en-JM&quot; &#039; English (Jamaica)\nCase &quot;1409&quot; : sLang = &quot;en-NZ&quot; &#039; English (New Zealand)\nCase &quot;3409&quot; : sLang = &quot;en-PH&quot; &#039; English (Philippines)\nCase &quot;1C09&quot; : sLang = &quot;en-ZA&quot; &#039; English (South Africa\nCase &quot;2C09&quot; : sLang = &quot;en-TT&quot; &#039; English (Trinidad and Tobago)\nCase &quot;809&quot; : sLang = &quot;en-GB&quot; &#039; English (United Kingdom)\nCase &quot;409&quot; : sLang = &quot;en-US&quot; &#039; English (United States)\nCase &quot;3009&quot; : sLang = &quot;en-ZW&quot; &#039; English (Zimbabwe)\nCase &quot;25&quot; : sLang = &quot;et&quot; &#039; Estonian\nCase &quot;425&quot; : sLang = &quot;et-EE&quot; &#039; Estonian (Estonia)\nCase &quot;38&quot; : sLang = &quot;fo&quot; &#039; Faroese\nCase &quot;438&quot; : sLang = &quot;fo-FO&quot; &#039; Faroese (Faroe Islands)\nCase &quot;29&quot; : sLang = &quot;fa&quot; &#039; Farsi\nCase &quot;429&quot; : sLang = &quot;fa-IR&quot; &#039; Farsi (Iran)\nCase &quot;B&quot; : sLang = &quot;fi&quot; &#039; Finnish\nCase &quot;40B&quot; : sLang = &quot;fi-FI&quot; &#039; Finnish (Finland)\nCase &quot;C&quot; : sLang = &quot;fr&quot; &#039; French\nCase &quot;80C&quot; : sLang = &quot;fr-BE&quot; &#039; French (Belgium)\nCase &quot;C0C&quot; : sLang = &quot;fr-CA&quot; &#039; French (Canada)\nCase &quot;40C&quot; : sLang = &quot;fr-FR&quot; &#039; French (France)\nCase &quot;140C&quot; : sLang = &quot;fr-LU&quot; &#039; French (Luxembourg)\nCase &quot;180C&quot; : sLang = &quot;fr-MC&quot; &#039; French (Monaco)\nCase &quot;100C&quot; : sLang = &quot;fr-CH&quot; &#039; French (Switzerland)\nCase &quot;56&quot; : sLang = &quot;gl&quot; &#039; Galician\nCase &quot;456&quot; : sLang = &quot;gl-ES&quot; &#039; Galician (Spain)\nCase &quot;37&quot; : sLang = &quot;ka&quot; &#039; Georgian\nCase &quot;437&quot; : sLang = &quot;ka-GE&quot; &#039; Georgian (Georgia)\nCase &quot;7&quot; : sLang = &quot;de&quot; &#039; German\nCase &quot;C07&quot; : sLang = &quot;de-AT&quot; &#039; German (Austria)\nCase &quot;407&quot; : sLang = &quot;de-DE&quot; &#039; German (Germany)\nCase &quot;1407&quot; : sLang = &quot;de-LI&quot; &#039; German (Liechtenstein)\nCase &quot;1007&quot; : sLang = &quot;de-LU&quot; &#039; German (Luxembourg)\nCase &quot;807&quot; : sLang = &quot;de-CH&quot; &#039; German (Switzerland)\nCase &quot;8&quot; : sLang = &quot;el&quot; &#039; Greek\nCase &quot;408&quot; : sLang = &quot;el-GR&quot; &#039; Greek (Greece)\nCase &quot;47&quot; : sLang = &quot;gu&quot; &#039; Gujarati\nCase &quot;447&quot; : sLang = &quot;gu-IN&quot; &#039; Gujarati (India)\nCase &quot;D&quot; : sLang = &quot;he&quot; &#039; Hebrew\nCase &quot;40D&quot; : sLang = &quot;he-IL&quot; &#039; Hebrew (Israel)\nCase &quot;39&quot; : sLang = &quot;hi&quot; &#039; Hindi\nCase &quot;439&quot; : sLang = &quot;hi-IN&quot; &#039; Hindi (India)\nCase &quot;E&quot; : sLang = &quot;hu&quot; &#039; Hungarian\nCase &quot;40E&quot; : sLang = &quot;hu-HU&quot; &#039; Hungarian (Hungary)\nCase &quot;F&quot; : sLang = &quot;is&quot; &#039; Icelandic\nCase &quot;40F&quot; : sLang = &quot;is-IS&quot; &#039; Icelandic (Iceland)\nCase &quot;21&quot; : sLang = &quot;id&quot; &#039; Indonesian\nCase &quot;421&quot; : sLang = &quot;id-ID&quot; &#039; Indonesian (Indonesia)\nCase &quot;10&quot; : sLang = &quot;it&quot; &#039; Italian\nCase &quot;410&quot; : sLang = &quot;it-IT&quot; &#039; Italian (Italy)\nCase &quot;810&quot; : sLang = &quot;it-CH&quot; &#039; Italian (Switzerland)\nCase &quot;11&quot; : sLang = &quot;ja&quot; &#039; Japanese\nCase &quot;411&quot; : sLang = &quot;ja-JP&quot; &#039; Japanese (Japan)\nCase &quot;4B&quot; : sLang = &quot;kn&quot; &#039; Kannada\nCase &quot;44B&quot; : sLang = &quot;kn-IN&quot; &#039; Kannada (India)\nCase &quot;3F&quot; : sLang = &quot;kk&quot; &#039; Kazakh\nCase &quot;43F&quot; : sLang = &quot;kk-KZ&quot; &#039; Kazakh (Kazakhstan)\nCase &quot;57&quot; : sLang = &quot;kok&quot; &#039; Konkani\nCase &quot;457&quot; : sLang = &quot;kok-IN&quot; &#039; Konkani (India)\nCase &quot;12&quot; : sLang = &quot;ko&quot; &#039; Korean\nCase &quot;412&quot; : sLang = &quot;ko-KR&quot; &#039; Korean (Korea)\nCase &quot;40&quot; : sLang = &quot;ky&quot; &#039; Kyrgyz\nCase &quot;440&quot; : sLang = &quot;ky-KG&quot; &#039; Kyrgyz (Kyrgyzstan)\nCase &quot;26&quot; : sLang = &quot;lv&quot; &#039; Latvian\nCase &quot;426&quot; : sLang = &quot;lv-LV&quot; &#039; Latvian (Latvia)\nCase &quot;27&quot; : sLang = &quot;lt&quot; &#039; Lithuanian\nCase &quot;427&quot; : sLang = &quot;lt-LT&quot; &#039; Lithuanian (Lithuania)\nCase &quot;2F&quot; : sLang = &quot;mk&quot; &#039; Macedonian\nCase &quot;42F&quot; : sLang = &quot;mk-MK&quot; &#039; Macedonian (Macedonia, FYROM)\nCase &quot;3E&quot; : sLang = &quot;ms&quot; &#039; Malay\nCase &quot;83E&quot; : sLang = &quot;ms-BN&quot; &#039; Malay (Brunei Darussalam)\nCase &quot;43E&quot; : sLang = &quot;ms-MY&quot; &#039; Malay (Malaysia)\nCase &quot;4E&quot; : sLang = &quot;mr&quot; &#039; Marathi\nCase &quot;44E&quot; : sLang = &quot;mr-IN&quot; &#039; Marathi (India)\nCase &quot;50&quot; : sLang = &quot;mn&quot; &#039; Mongolian\nCase &quot;450&quot; : sLang = &quot;mn-MN&quot; &#039; Mongolian (Mongolia)\nCase &quot;14&quot; : sLang = &quot;no&quot; &#039; Norwegian\nCase &quot;414&quot; : sLang = &quot;nb-NO&quot; &#039; Norwegian (Bokm\u00e5l, Norway)\nCase &quot;814&quot; : sLang = &quot;nn-NO&quot; &#039; Norwegian (Nynorsk, Norway)\nCase &quot;15&quot; : sLang = &quot;pl&quot; &#039; Polish\nCase &quot;415&quot; : sLang = &quot;pl-PL&quot; &#039; Polish (Poland)\nCase &quot;16&quot; : sLang = &quot;pt&quot; &#039; Portuguese\nCase &quot;416&quot; : sLang = &quot;pt-BR&quot; &#039; Portuguese (Brazil)\nCase &quot;816&quot; : sLang = &quot;pt-PT&quot; &#039; Portuguese (Portugal)\nCase &quot;46&quot; : sLang = &quot;pa&quot; &#039; Punjabi\nCase &quot;446&quot; : sLang = &quot;pa-IN&quot; &#039; Punjabi (India)\nCase &quot;18&quot; : sLang = &quot;ro&quot; &#039; Romanian\nCase &quot;418&quot; : sLang = &quot;ro-RO&quot; &#039; Romanian (Romania)\nCase &quot;19&quot; : sLang = &quot;ru&quot; &#039; Russian\nCase &quot;419&quot; : sLang = &quot;ru-RU&quot; &#039; Russian (Russia)\nCase &quot;4F&quot; : sLang = &quot;sa&quot; &#039; Sanskrit\nCase &quot;44F&quot; : sLang = &quot;sa-IN&quot; &#039; Sanskrit (India)\nCase &quot;C1A&quot; : sLang = &quot;sr-Cyrl-CS&quot; &#039; Serbian (Serbia, Cyrillic)\nCase &quot;81A&quot; : sLang = &quot;sr-Latn-CS&quot; &#039; Serbian (Serbia, Latin)\nCase &quot;1B&quot; : sLang = &quot;sk&quot; &#039; Slovak\nCase &quot;41B&quot; : sLang = &quot;sk-SK&quot; &#039; Slovak (Slovakia)\nCase &quot;24&quot; : sLang = &quot;sl&quot; &#039; Slovenian\nCase &quot;424&quot; : sLang = &quot;sl-SI&quot; &#039; Slovenian (Slovenia)\nCase &quot;A&quot; : sLang = &quot;es&quot; &#039; Spanish\nCase &quot;2C0A&quot; : sLang = &quot;es-AR&quot; &#039; Spanish (Argentina)\nCase &quot;400A&quot; : sLang = &quot;es-BO&quot; &#039; Spanish (Bolivia)\nCase &quot;340A&quot; : sLang = &quot;es-CL&quot; &#039; Spanish (Chile)\nCase &quot;240A&quot; : sLang = &quot;es-CO&quot; &#039; Spanish (Colombia)\nCase &quot;140A&quot; : sLang = &quot;es-CR&quot; &#039; Spanish (Costa Rica)\nCase &quot;1C0A&quot; : sLang = &quot;es-DO&quot; &#039; Spanish (Dominican Republic)\nCase &quot;300A&quot; : sLang = &quot;es-EC&quot; &#039; Spanish (Ecuador)\nCase &quot;440A&quot; : sLang = &quot;es-SV&quot; &#039; Spanish (El Salvador)\nCase &quot;100A&quot; : sLang = &quot;es-GT&quot; &#039; Spanish (Guatemala)\nCase &quot;480A&quot; : sLang = &quot;es-HN&quot; &#039; Spanish (Honduras)\nCase &quot;80A&quot; : sLang = &quot;es-MX&quot; &#039; Spanish (Mexico)\nCase &quot;4C0A&quot; : sLang = &quot;es-NI&quot; &#039; Spanish (Nicaragua)\nCase &quot;180A&quot; : sLang = &quot;es-PA&quot; &#039; Spanish (Panama)\nCase &quot;3C0A&quot; : sLang = &quot;es-PY&quot; &#039; Spanish (Paraguay)\nCase &quot;280A&quot; : sLang = &quot;es-PE&quot; &#039; Spanish (Peru)\nCase &quot;500A&quot; : sLang = &quot;es-PR&quot; &#039; Spanish (Puerto Rico)\nCase &quot;C0A&quot; : sLang = &quot;es-ES&quot; &#039; Spanish (Spain)\nCase &quot;380A&quot; : sLang = &quot;es-UY&quot; &#039; Spanish (Uruguay)\nCase &quot;200A&quot; : sLang = &quot;es-VE&quot; &#039; Spanish (Venezuela)\nCase &quot;41&quot; : sLang = &quot;sw&quot; &#039; Swahili\nCase &quot;441&quot; : sLang = &quot;sw-KE&quot; &#039; Swahili (Kenya)\nCase &quot;1D&quot; : sLang = &quot;sv&quot; &#039; Swedish\nCase &quot;81D&quot; : sLang = &quot;sv-FI&quot; &#039; Swedish (Finland)\nCase &quot;41D&quot; : sLang = &quot;sv-SE&quot; &#039; Swedish (Sweden)\nCase &quot;5A&quot; : sLang = &quot;syr&quot; &#039; Syriac\nCase &quot;45A&quot; : sLang = &quot;syr-SY&quot; &#039; Syriac (Syria)\nCase &quot;49&quot; : sLang = &quot;ta&quot; &#039; Tamil\nCase &quot;449&quot; : sLang = &quot;ta-IN&quot; &#039; Tamil (India)\nCase &quot;44&quot; : sLang = &quot;tt&quot; &#039; Tatar\nCase &quot;444&quot; : sLang = &quot;tt-RU&quot; &#039; Tatar (Russia)\nCase &quot;4A&quot; : sLang = &quot;te&quot; &#039; Telugu\nCase &quot;44A&quot; : sLang = &quot;te-IN&quot; &#039; Telugu (India)\nCase &quot;1E&quot; : sLang = &quot;th&quot; &#039; Thai\nCase &quot;41E&quot; : sLang = &quot;th-TH&quot; &#039; Thai (Thailand)\nCase &quot;1F&quot; : sLang = &quot;tr&quot; &#039; Turkish\nCase &quot;41F&quot; : sLang = &quot;tr-TR&quot; &#039; Turkish (Turkey)\nCase &quot;22&quot; : sLang = &quot;uk&quot; &#039; Ukrainian\nCase &quot;422&quot; : sLang = &quot;uk-UA&quot; &#039; Ukrainian (Ukraine)\nCase &quot;20&quot; : sLang = &quot;ur&quot; &#039; Urdu\nCase &quot;420&quot; : sLang = &quot;ur-PK&quot; &#039; Urdu (Pakistan)\nCase &quot;43&quot; : sLang = &quot;uz&quot; &#039; Uzbek\nCase &quot;843&quot; : sLang = &quot;uz-Cyrl-UZ&quot; &#039; Uzbek (Uzbekistan, Cyrillic)\nCase &quot;443&quot; : sLang = &quot;uz-Latn-UZ&quot; &#039; Uzbek (Uzbekistan, Latin)\nCase &quot;2A&quot; : sLang = &quot;vi&quot; &#039; Vietnamese\nCase &quot;42A&quot; : sLang = &quot;vi-VN&quot; &#039; Vietnamese (Vietnam)\nCase Else : sLang = &quot;&quot;\nEnd Select\nGetCultureInfo = sLang\nEnd Function&#039;=======================================================================================================<\/code><\/pre>\n\n\n\n<p>\u043a\u043b\u0430\u0434\u0435\u043c \u0444\u0430\u0439\u043b \u0432 c:\\temp\\ \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0441 \u043d\u0435\u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u043c\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438.<\/p>\n\n\n\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c CMD \u043e\u0442 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 c:\\windows\\installer<\/p>\n\n\n\n<p>\u0434\u0430\u043b\u0435\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 cscript.exe c:\\temp\\OpUtil.vbs\/RepairCache\/srestorelocation=c:\\temp\\<\/p>\n\n\n\n<p>Open up a PowerShell window and run Get-SPProduct -local to update the farm\u2019s configuration database.<\/p>\n\n\n\n<p>Run psconfig.exe to update everything and now you should be ready to install your updates.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0414\u043b\u044f \u0440\u0435\u0448\u0435\u043d\u0438\u044f \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0432\u0435\u0440 SharePoint \u0442\u043e\u0439 \u0436\u0435 \u0432\u0435\u0440\u0441\u0438\u0438, \u0433\u0434\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u044e\u0442\u0441\u044f. \u0421\u043a\u043e\u043f\u0438\u0440\u0443\u0439\u0442\u0435 \u043f\u0430\u043f\u043a\u0443 c:\\windows\\installer \u0441 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430 SharePoint \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e c:\\temp\\ \u043f\u0430\u043f\u043a\u0443 \u043d\u0430 \u0432\u0430\u0448 \u043f\u043b\u043e\u0445\u043e\u0439 \u0441\u0435\u0440\u0432\u0435\u0440. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b OpUtil.vbs \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0433\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044f: \u043a\u043b\u0430\u0434\u0435\u043c \u0444\u0430\u0439\u043b \u0432 c:\\temp\\ \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0441 \u043d\u0435\u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u043c\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f\u043c\u0438. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c CMD \u043e\u0442 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430, \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 c:\\windows\\installer \u0434\u0430\u043b\u0435\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 cscript.exe c:\\temp\\OpUtil.vbs\/RepairCache\/srestorelocation=c:\\temp\\ Open [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[33],"class_list":["post-452","post","type-post","status-publish","format-standard","hentry","category-sharepoint","tag-sharepoint"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/posts\/452","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=452"}],"version-history":[{"count":1,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/posts\/452\/revisions"}],"predecessor-version":[{"id":453,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=\/wp\/v2\/posts\/452\/revisions\/453"}],"wp:attachment":[{"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=452"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=452"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kb.astrocroc.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=452"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}