可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm using InstallShield 2013 to make a Basic MSI installer for an application that requires Windows Platform Update KB2670838.
For .NET frameworks and other requirements, I select them in InstallShield in the Redistributables section. KB2670838 is not available.
If I download KB2670838 from Microsoft I get a .msu
file. Can that be included in the installer somehow so that it automatically installs if needed? If not, is there a way to stop the install and tell the user that "KB2670838 is required but not installed. Get it here..."?
回答1:
@Glytzhkof Good point. So how do I get InstallShield to abort and give the user a nice message so they know what to do? – shoelzer 1 hour ago
I will just add a new answer then - too long to write in a comment.
- Locate the file details you need to scan for under "For More
Information : File Information" in this kdb article:
http://support.microsoft.com/kb/2670838/en
- Select a few files to scan for and add as file searches in Installshield (see below screenshot). You specify a property for each file (FILE1FOUND, FILE2FOUND, FILE3FOUND, etc...), and if the search matches the file details (version, size, date, etc...) the property is set to the full path of the file. Otherwise the property is undefined or set to a default value (screenshot shows predefined search, and not file search, but you get the idea).
- Finally you add LaunchCondition entries for each file to ensure that all files you have selected to check are the correct version or higher. I guess this is in Prerequisites or similar - I can't recall. Open the compiled MSI and check that it looks like the LaunchConditon table.
For the record: (not part of above suggestion)
Personally I am in favor of coding a single script for complex logic like this to ensure the logic can be inspected as a whole and crucially tested as a whole outside the MSI file. It is also good to add comments to such code to explain what the script is checking, and why (helps corporate deployment). A script can be run through dozens of tests against the machine directly without recompiling the MSI. This can save a lot of time if the logic is complex. If you write a compiled dll you can show a message box and attach the visual studio debugger to the msiexec.exe process (client or server depending on what context your custom action is running in) and step-through the code whilst embedded in the MSI, but this seems out of scope for your scenario. Just want to mention it for other people who might read this. Also check Stefan Kruger's installsite.com for more information on complex setup debugging like this.
It is important to note that scripting is never generally recommended for scenarios where the script makes changes to the system - if there is a built-in MSI way to achieve the same result. The reason for this is that a script that makes changes to a machine will need a separate rollback-operation to be specified for it for the MSI to follow best practice. This can be a spectacular amount of work and complexity to get right. The above script would only check system conditions, so there is no need for rollback support.
回答2:
In InstallShield, you should typically deliver this sort of update as a prerequisite (Tools > Prerequisite Editor), or as a package included in a Suite (reference [SystemFolder]wusa.exe
to install an .msu
file). In both cases this keeps the redistributable installation logically separate from your package's installation, while providing your users a single installer experience.
Glytzhkof mentions several really good points about how to determine whether the update has been installed. You will want to incorporate these into your conditions (on the prerequisite or suite package), and also into detecting the update or lack thereof in your .msi
package so it can abort if the required update has not been installed by the time the .msi
is launched.
回答3:
The Add/Remove programs list in the registry could help you get a rough idea of what's installed:
- HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
It seems this doesn't provide a full list of what is installed though: http://social.technet.microsoft.com/Forums/windows/en-US/d913471a-d7fb-448d-869b-da9025dcc943/where-does-addremove-programs-get-its-information-from-in-the-registry?forum=w7itprogeneral
Another way may be to use the file information from the knowledge base article:
http://support.microsoft.com/kb/2670838/en (For More Information : File Information) and use WIX / MSI's AppSearch / LaunchCondition feature. That should do the trick, though I find the syntax a bit counterintuitive.
Another approach is to write a custom action and combine these two sources (add /remove entry and file info). Such a custom action will make no changes to the system, and is hence less problematic than other custom actions that cause rollback-problems. I find it easier to test and maintain a custom action in case there are further prerequisites that are needed at some point. This is a matter of taste though. I just find it easier to run a prerequisite script against a selection of files to test that it identifies them correctly and run through as expected than to keep running the MSI file for every test.
Here is a similar question with some pointers from superuser.com:
https://superuser.com/questions/521175/determine-if-windows-hotfix-has-been-applied
And another link to serverfault.com (system administration site). Nice approach using PowerShell which can certainly be migrated to a custom action:
https://serverfault.com/questions/312778/determine-if-user-has-hotfix-981889-installed
Even more serverfault.com stuff involving update.exe, WMI and a Powershell script to view all installed hotfixes:
https://serverfault.com/questions/263847/how-can-i-query-my-system-via-command-line-to-see-if-a-kb-patch-is-installed . Recommended read. Microsoft: http://technet.microsoft.com/en-us/library/hh849836.aspx
PSInfo appears to be able to show installed hotfixes: http://technet.microsoft.com/en-us/sysinternals/bb897550
回答4:
Let me try and add a reference style answer since my other answer is a bit organic to say the least at this point - I will leave it in since it contains an MSI discussion. See MSI recommendation in the middle section below:
WMI:
wmic qfe where "HotfixID = 'KB973687'"
PowerShell: (just get-hotfix for full list)
get-hotfix | findstr "981889"
SystemInfo (remove arguments for list format):
systeminfo /fo csv
PSInfo (seems to not list everything on all machines, and may not run silently properly):
PSinfo -h
Registry (apparently not complete list of hotfixes):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
For MSI custom action use, I would actually use a custom action that inspects file versions as explained in my other answer. Very reliable, and takes into account that the hotfix may be deprecated whilst the files are still up to date.
References:
- Recommended: https://serverfault.com/questions/263847/how-can-i-query-my-system-via-command-line-to-see-if-a-kb-patch-is-installed
- How do I get a list of installed updates and hotfixes?
- SystemInfo: https://serverfault.com/questions/334552/how-to-check-that-a-known-windows-vulnerability-has-been-patched
- http://windowsitpro.com/scripting/get-hotfix-information-quickly-wmic
- https://serverfault.com/questions/69467/list-all-hotfixes-applied-to-windows-server
- wmic in general: http://technet.microsoft.com/en-us/library/bb742610.aspx
- Recommended: http://www.dedoimedo.com/computers/windows-wmic.html
- http://www.techsupportalert.com/content/quick-and-easy-way-list-all-windows-updates-installed-your-system.htm
- http://pario.no/2011/06/19/list-installed-windows-updates-using-wmic/
回答5:
Had the same issue and solved it by adding a prerequisite of a PowerShell script and a batch file to execute it.
The pre.ps1 file looks something like this:
function TestConnection
{
Test-Connection -ComputerName "8.8.8.8" -Quiet
}
get-hotfix -id KB2670838
if(!$?){
#SourceURI = "https://download.microsoft.com/download/1/4/9/14936FE9-4D16-4019-A093-5E00182609EB/Windows6.1-KB2670838-x64.msu";
#$FileName = $SourceURI .Split('/')[-1]
#$BinPath = Join-Path $DownloadPath -ChildPath $FileName
Invoke-Webrequest -Uri $SourceURI -OutFile $BinPath
#Start-Process -FilePath $BinPath -ArgumentList "/q /norestart" -Wait -NoNewWindow
}
the pre.cmd file looks something like this:
@echo off
::set PS_FILE=%~dp0Prerequisite.ps1
set PS_FILE=%~dpn0.ps1
set PS_EXEC_PATH=%SystemRoot%\sysnative\WindowsPowerShell\v1.0\
set PS_EXEC_PATH=%SystemRoot%\System32\WindowsPowerShell\v1.0\
::set PS_EXEC_PATH=%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\
set PS_EXEC_PATH=
set PS_EXEC=%PS_EXEC_PATH%powershell.exe
echo %PS_EXEC%
echo %PS_FILE%
::%PS_EXEC% -file %PS_FILE% set-executionpolicy remotesigned
::%PS_EXEC% -NoProfile -ExecutionPolicy Bypass -Command "& '%PS_FILE%'"
::This is with admin rights
%PS_EXEC% -NoProfile -Command "& {Start-Process PowerShell.exe -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%PS_FILE%""' -Verb RunAs}"
::pause