// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. namespace WixToolset.Dtf.WindowsInstaller { using System; using System.IO; using System.Text; using System.Resources; using System.Reflection; using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; public static partial class Installer { /// /// Gets the current version of the installer. /// public static Version Version { get { // TODO: Use the extended form of version info to get the 4th component of the verison. uint[] dllVersionInfo = new uint[5]; dllVersionInfo[0] = 20; int hr = NativeMethods.DllGetVersion(dllVersionInfo); if (hr != 0) { Marshal.ThrowExceptionForHR(hr); } return new Version((int) dllVersionInfo[1], (int) dllVersionInfo[2], (int) dllVersionInfo[3]); } } internal static ResourceManager ErrorResources { get { if (errorResources == null) { errorResources = new ResourceManager(typeof(Installer).Namespace + ".Errors", typeof(Installer).Assembly); } return errorResources; } } /// /// Gets a Windows Installer error message in the system default language. /// /// The error number. /// The message string, or null if the error message is not found. ///

/// The returned string may have tokens such as [2] and [3] that are meant to be substituted /// with context-specific values. ///

/// Error numbers greater than 2000 refer to MSI "internal" errors, and are always /// returned in English. ///

public static string GetErrorMessage(int errorNumber) { return Installer.GetErrorMessage(errorNumber, null); } /// /// Gets a Windows Installer error message in a specified language. /// /// The error number. /// The locale for the message. /// The message string, or null if the error message or locale is not found. ///

/// The returned string may have tokens such as [2] and [3] that are meant to be substituted /// with context-specific values. ///

/// Error numbers greater than 2000 refer to MSI "internal" errors, and are always /// returned in English. ///

[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")] public static string GetErrorMessage(int errorNumber, CultureInfo culture) { if (culture == null) { culture = CultureInfo.CurrentCulture; } string msg = Installer.ErrorResources.GetString( errorNumber.ToString(CultureInfo.InvariantCulture.NumberFormat), culture); if (msg == null) { string msiMsgModule = Path.Combine( Environment.SystemDirectory, "msimsg.dll"); msg = Installer.GetMessageFromModule( msiMsgModule, errorNumber, culture); } return msg; } private static string GetMessageFromModule( string modulePath, int errorNumber, CultureInfo culture) { const uint LOAD_LIBRARY_AS_DATAFILE = 2; const int RT_RCDATA = 10; IntPtr msgModule = NativeMethods.LoadLibraryEx( modulePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); if (msgModule == IntPtr.Zero) { return null; } try { // On pre-Vista systems, the messages are stored as RCDATA resources. int lcid = (culture == CultureInfo.InvariantCulture) ? 0 : culture.LCID; IntPtr resourceInfo = NativeMethods.FindResourceEx( msgModule, new IntPtr(RT_RCDATA), new IntPtr(errorNumber), (ushort) lcid); if (resourceInfo != IntPtr.Zero) { IntPtr resourceData = NativeMethods.LoadResource( msgModule, resourceInfo); IntPtr resourcePtr = NativeMethods.LockResource(resourceData); if (lcid == 0) { string msg = Marshal.PtrToStringAnsi(resourcePtr); return msg; } else { int len = 0; while (Marshal.ReadByte(resourcePtr, len) != 0) { len++; } byte[] msgBytes = new byte[len + 1]; Marshal.Copy(resourcePtr, msgBytes, 0, msgBytes.Length); Encoding encoding = Encoding.GetEncoding( culture.TextInfo.ANSICodePage); string msg = encoding.GetString(msgBytes); return msg; } } else { // On Vista (and above?), the messages are stored in the module message table. // They're actually in MUI files, and the redirection happens automatically here. const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800; const uint MESSAGE_OFFSET = 20000; // Not documented, but observed on Vista StringBuilder buf = new StringBuilder(1024); uint formatCount = NativeMethods.FormatMessage( FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, msgModule, (uint) errorNumber + MESSAGE_OFFSET, (ushort) lcid, buf, (uint) buf.Capacity, IntPtr.Zero); return formatCount != 0 ? buf.ToString().Trim() : null; } } finally { NativeMethods.FreeLibrary(msgModule); } } /// /// Gets a formatted Windows Installer error message in the system default language. /// /// Error record containing the error number in the first field, and /// error-specific parameters in the other fields. /// The message string, or null if the error message is not found. ///

/// Error numbers greater than 2000 refer to MSI "internal" errors, and are always /// returned in English. ///

public static string GetErrorMessage(Record errorRecord) { return Installer.GetErrorMessage(errorRecord, null); } /// /// Gets a formatted Windows Installer error message in a specified language. /// /// Error record containing the error number in the first field, and /// error-specific parameters in the other fields. /// The locale for the message. /// The message string, or null if the error message or locale is not found. ///

/// Error numbers greater than 2000 refer to MSI "internal" errors, and are always /// returned in English. ///

public static string GetErrorMessage(Record errorRecord, CultureInfo culture) { if (errorRecord == null) { throw new ArgumentNullException("errorRecord"); } int errorNumber; if (errorRecord.FieldCount < 1 || (errorNumber = (int) errorRecord.GetInteger(1)) == 0) { throw new ArgumentOutOfRangeException("errorRecord"); } string msg = Installer.GetErrorMessage(errorNumber, culture); if (msg != null) { errorRecord.FormatString = msg; msg = errorRecord.ToString((IFormatProvider)null); } return msg; } /// /// Gets the version string of the path specified using the format that the installer /// expects to find it in in the database. /// /// Path to the file /// Version string in the "#.#.#.#" format, or an empty string if the file /// does not contain version information /// the file does not exist or could not be read ///

/// Win32 MSI API: /// MsiGetFileVersion ///

public static string GetFileVersion(string path) { StringBuilder version = new StringBuilder(20); uint verBufSize = 0, langBufSize = 0; uint ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { version.Capacity = (int) ++verBufSize; ret = NativeMethods.MsiGetFileVersion(path, version, ref verBufSize, null, ref langBufSize); } if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID) { if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || ret == (uint) NativeMethods.Error.ACCESS_DENIED) { throw new FileNotFoundException(null, path); } else { throw InstallerException.ExceptionFromReturnCode(ret); } } return version.ToString(); } /// /// Gets the language string of the path specified using the format that the installer /// expects to find them in in the database. /// /// Path to the file /// Language string in the form of a decimal language ID, or an empty string if the file /// does not contain a language ID /// the file does not exist or could not be read ///

/// Win32 MSI API: /// MsiGetFileVersion ///

public static string GetFileLanguage(string path) { StringBuilder language = new StringBuilder("", 10); uint verBufSize = 0, langBufSize = 0; uint ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize); if (ret == (uint) NativeMethods.Error.MORE_DATA) { language.Capacity = (int) ++langBufSize; ret = NativeMethods.MsiGetFileVersion(path, null, ref verBufSize, language, ref langBufSize); } if (ret != 0 && ret != (uint) NativeMethods.Error.FILE_INVALID) { if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || ret == (uint) NativeMethods.Error.ACCESS_DENIED) { throw new FileNotFoundException(null, path); } else { throw InstallerException.ExceptionFromReturnCode(ret); } } return language.ToString(); } /// /// Gets a 128-bit hash of the specified file. /// /// Path to the file /// Integer array of length 4 which receives the /// four 32-bit parts of the hash value. /// the file does not exist or /// could not be read ///

/// Win32 MSI API: /// MsiGetFileHash ///

public static void GetFileHash(string path, int[] hash) { if (hash == null) { throw new ArgumentNullException("hash"); } uint[] tempHash = new uint[5]; tempHash[0] = 20; uint ret = NativeMethods.MsiGetFileHash(path, 0, tempHash); if (ret != 0) { if (ret == (uint) NativeMethods.Error.FILE_NOT_FOUND || ret == (uint) NativeMethods.Error.ACCESS_DENIED) { throw new FileNotFoundException(null, path); } else { throw InstallerException.ExceptionFromReturnCode(ret); } } for (int i = 0; i < 4; i++) { hash[i] = unchecked ((int) tempHash[i + 1]); } } /// /// Examines a shortcut and returns its product, feature name, and component if available. /// /// Full path to a shortcut /// ShortcutTarget structure containing target product code, feature, and component code ///

/// Win32 MSI API: /// MsiGetShortcutTarget ///

public static ShortcutTarget GetShortcutTarget(string shortcut) { StringBuilder productBuf = new StringBuilder(40); StringBuilder featureBuf = new StringBuilder(40); StringBuilder componentBuf = new StringBuilder(40); uint ret = NativeMethods.MsiGetShortcutTarget(shortcut, productBuf, featureBuf, componentBuf); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return new ShortcutTarget( productBuf.Length > 0 ? productBuf.ToString() : null, featureBuf.Length > 0 ? featureBuf.ToString() : null, componentBuf.Length > 0 ? componentBuf.ToString() : null); } /// /// Verifies that the given file is an installation package. /// /// Path to the package /// True if the file is an installation package; false otherwise. /// the specified package file does not exist /// the package file could not be opened ///

/// Win32 MSI API: /// MsiVerifyPackage ///

public static bool VerifyPackage(string packagePath) { if (String.IsNullOrEmpty(packagePath)) { throw new ArgumentNullException("packagePath"); } if (!File.Exists(packagePath)) { throw new FileNotFoundException(null, packagePath); } uint ret = NativeMethods.MsiVerifyPackage(packagePath); if (ret == (uint) NativeMethods.Error.INSTALL_PACKAGE_INVALID) { return false; } else if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } return true; } /// /// [MSI 4.0] Gets the list of files that can be updated by one or more patches. /// /// ProductCode (GUID) of the product which is /// the target of the patches /// list of file paths of one or more patches to be /// analyzed /// List of absolute paths of files that can be updated when the /// patches are applied on this system. ///

/// Win32 MSI API: /// MsiGetPatchFileList ///

public static IList GetPatchFileList(string productCode, IList patches) { if (String.IsNullOrEmpty(productCode)) { throw new ArgumentNullException("productCode"); } if (patches == null || patches.Count == 0) { throw new ArgumentNullException("patches"); } StringBuilder patchList = new StringBuilder(); foreach (string patch in patches) { if (patch != null) { if (patchList.Length != 0) { patchList.Append(';'); } patchList.Append(patch); } } if (patchList.Length == 0) { throw new ArgumentNullException("patches"); } IntPtr phFileRecords; uint cFiles; uint ret = NativeMethods.MsiGetPatchFileList( productCode, patchList.ToString(), out cFiles, out phFileRecords); if (ret != 0) { throw InstallerException.ExceptionFromReturnCode(ret); } List files = new List(); for (uint i = 0; i < cFiles; i++) { int hFileRec = Marshal.ReadInt32(phFileRecords, (int) i); using (Record fileRec = new Record(hFileRec, true, null)) { files.Add(fileRec.GetString(1)); } } return files; } } }