/// <summary> /// Applies a patch package to the database, resulting in an installation package that /// has the patch built-in. /// </summary> /// <param name="patchPackage">The patch package to be applied</param> /// <param name="transform">Optional name of the specific transform to apply. /// This parameter is usually left null, which causes the patch to be searched for /// a transform that is valid to apply to this database.</param> /// <remarks> /// If the patch contains any binary file patches, they will not immediately be applied /// to the target files, though they will at installation time. /// <p>After calling this method you can use <see cref="Consolidate"/> to apply /// the file patches immediately and also discard any outdated files from the package.</p> /// </remarks> public void ApplyPatch(PatchPackage patchPackage, string transform) { if(patchPackage == null) throw new ArgumentNullException("patchPackage"); this.LogMessage("Applying patch file {0} to database {1}", patchPackage.FilePath, this.FilePath); if(transform == null) { this.LogMessage("No transform specified; searching for valid patch transform"); string[] validTransforms = patchPackage.GetValidTransforms(this); if(validTransforms.Length == 0) { this.LogMessage("No valid patch transform was found"); throw new InvalidOperationException("No valid patch transform was found."); } transform = validTransforms[0]; } this.LogMessage("Patch transform = {0}", transform); string patchPrefix = Path.GetFileNameWithoutExtension(patchPackage.FilePath) + "_"; string specialTransform = "#" + transform; Directory.CreateDirectory(this.TempDirectory); this.LogMessage("Extracting substorage {0}", transform); string transformFile = Path.Combine(this.TempDirectory, patchPrefix + Path.GetFileNameWithoutExtension(transform) + ".mst"); patchPackage.ExtractTransform(transform, transformFile); this.LogMessage("Extracting substorage {0}", specialTransform); string specialTransformFile = Path.Combine(this.TempDirectory, patchPrefix + Path.GetFileNameWithoutExtension(specialTransform) + ".mst"); patchPackage.ExtractTransform(specialTransform, specialTransformFile); if (this.Tables.Contains("Patch") && !this.Tables["Patch"].Columns.Contains("_StreamRef")) { if(this.CountRows("Patch") > 0) { this.LogMessage("Warning: non-empty Patch table exists without StreamRef_ column; " + "patch transform may fail"); } else { this.Execute("DROP TABLE `Patch`"); this.Execute("CREATE TABLE `Patch` (`File_` CHAR(72) NOT NULL, " + "`Sequence` INTEGER NOT NULL, `PatchSize` LONG NOT NULL, " + "`Attributes` INTEGER NOT NULL, `Header` OBJECT, `StreamRef_` CHAR(72) " + "PRIMARY KEY `File_`, `Sequence`)"); } } this.LogMessage("Applying transform {0} to database", transform); this.ApplyTransform(transformFile); this.LogMessage("Applying transform {0} to database", specialTransform); this.ApplyTransform(specialTransformFile); if (this.Tables.Contains("MsiPatchHeaders") && this.CountRows("MsiPatchHeaders") > 0 && (!this.Tables.Contains("Patch") || this.CountRows("Patch", "`StreamRef_` <> ''") == 0)) { this.LogMessage("Error: patch transform failed because of missing Patch.StreamRef_ column"); throw new InstallerException("Patch transform failed because of missing Patch.StreamRef_ column"); } IList<int> mediaIds = this.ExecuteIntegerQuery("SELECT `Media_` FROM `PatchPackage` " + "WHERE `PatchId` = '{0}'", patchPackage.PatchCode); if (mediaIds.Count == 0) { this.LogMessage("Warning: PatchPackage Media record not found -- " + "skipping inclusion of patch cabinet"); } else { int patchMediaDiskId = mediaIds[0]; IList<string> patchCabinets = this.ExecuteStringQuery("SELECT `Cabinet` FROM `Media` " + "WHERE `DiskId` = {0}", patchMediaDiskId); if(patchCabinets.Count == 0) { this.LogMessage("Patch cabinet record not found"); throw new InstallerException("Patch cabinet record not found."); } string patchCabinet = patchCabinets[0]; this.LogMessage("Patch cabinet = {0}", patchCabinet); if(!patchCabinet.StartsWith("#", StringComparison.Ordinal)) { this.LogMessage("Error: Patch cabinet must be embedded"); throw new InstallerException("Patch cabinet must be embedded."); } patchCabinet = patchCabinet.Substring(1); string renamePatchCabinet = patchPrefix + patchCabinet; const int HIGH_DISKID = 30000; // Must not collide with other patch media DiskIDs int renamePatchMediaDiskId = HIGH_DISKID; while (this.CountRows("Media", "`DiskId` = " + renamePatchMediaDiskId) > 0) renamePatchMediaDiskId++; // Since the patch cab is now embedded in the MSI, it shouldn't have a separate disk prompt/source this.LogMessage("Renaming the patch media record"); int lastSeq = Convert.ToInt32(this.ExecuteScalar("SELECT `LastSequence` FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId)); this.Execute("DELETE FROM `Media` WHERE `DiskId` = {0}", patchMediaDiskId); this.Execute("INSERT INTO `Media` (`DiskId`, `LastSequence`, `Cabinet`) VALUES ({0}, '{1}', '#{2}')", renamePatchMediaDiskId, lastSeq, renamePatchCabinet); this.Execute("UPDATE `PatchPackage` SET `Media_` = {0} WHERE `PatchId` = '{1}'", renamePatchMediaDiskId, patchPackage.PatchCode); this.LogMessage("Copying patch cabinet: {0}", patchCabinet); string patchCabFile = Path.Combine(this.TempDirectory, Path.GetFileNameWithoutExtension(patchCabinet) + ".cab"); using(View streamView = patchPackage.OpenView("SELECT `Name`, `Data` FROM `_Streams` " + "WHERE `Name` = '{0}'", patchCabinet)) { streamView.Execute(); Record streamRec = streamView.Fetch(); if(streamRec == null) { this.LogMessage("Error: Patch cabinet not found"); throw new InstallerException("Patch cabinet not found."); } using(streamRec) { streamRec.GetStream(2, patchCabFile); } } using(Record patchCabRec = new Record(2)) { patchCabRec[1] = patchCabinet; patchCabRec.SetStream(2, patchCabFile); this.Execute("INSERT INTO `_Streams` (`Name`, `Data`) VALUES (?, ?)", patchCabRec); } this.LogMessage("Ensuring PatchFiles action exists in InstallExecuteSequence table"); if (this.Tables.Contains("InstallExecuteSequence")) { if(this.CountRows("InstallExecuteSequence", "`Action` = 'PatchFiles'") == 0) { IList<int> installFilesSeqList = this.ExecuteIntegerQuery("SELECT `Sequence` " + "FROM `InstallExecuteSequence` WHERE `Action` = 'InstallFiles'"); short installFilesSeq = (short) (installFilesSeqList.Count != 0 ? installFilesSeqList[0] : 0); this.Execute("INSERT INTO `InstallExecuteSequence` (`Action`, `Sequence`) " + "VALUES ('PatchFiles', {0})", installFilesSeq + 1); } } // Patch-added files need to be marked always-compressed this.LogMessage("Adjusting attributes of patch-added files"); using(View fileView = this.OpenView("SELECT `File`, `Attributes`, `Sequence` " + "FROM `File` ORDER BY `Sequence`")) { fileView.Execute(); foreach (Record fileRec in fileView) using(fileRec) { int fileAttributes = fileRec.GetInteger(2); if ((fileAttributes & (int) Microsoft.Deployment.WindowsInstaller.FileAttributes.PatchAdded) != 0) { fileAttributes = (fileAttributes | (int) Microsoft.Deployment.WindowsInstaller.FileAttributes.Compressed) & ~(int) Microsoft.Deployment.WindowsInstaller.FileAttributes.NonCompressed & ~(int) Microsoft.Deployment.WindowsInstaller.FileAttributes.PatchAdded; fileRec[2] = fileAttributes; fileView.Update(fileRec); } } } } this.LogMessage("Applying new summary info from patch package"); this.SummaryInfo.RevisionNumber = this.Property["PATCHNEWPACKAGECODE"]; this.SummaryInfo.Subject = this.Property["PATCHNEWSUMMARYSUBJECT"]; this.SummaryInfo.Comments = this.Property["PATCHNEWSUMMARYCOMMENTS"]; this.SummaryInfo.Persist(); this.Property["PATCHNEWPACKAGECODE" ] = null; this.Property["PATCHNEWSUMMARYSUBJECT" ] = null; this.Property["PATCHNEWSUMMARYCOMMENTS"] = null; this.LogMessage("Patch application finished"); }
/// <summary> /// Writes a <see cref="SummaryInfo"/> object for each supported file type to the pipeline. /// </summary> /// <param name="item">A <see cref="PSObject"/> representing the file system object to process.</param> protected override void ProcessItem(PSObject item) { var path = item.GetPropertyValue<string>("PSPath"); var providerPath = this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(path); // Make sure the file exists and is a patch. var type = FileInfo.GetFileTypeInternal(providerPath); if (FileType.Package == type || FileType.Patch == type || FileType.Transform == type) { using (var info = new Deployment.WindowsInstaller.SummaryInfo(providerPath, false)) { var copy = new SummaryInfo(info); var obj = PSObject.AsPSObject(copy); // Add the class type as the first type name. var name = typeof(SummaryInfo).FullName + "#" + type; obj.TypeNames.Insert(0, name); // Attach the original PSPath and write to the pipeline. obj.SetPropertyValue("PSPath", path); this.WriteObject(obj); } } // Enumerate transforms in the patch. if (FileType.Patch == type && this.IncludeTransforms) { using (var patch = new PatchPackage(providerPath)) { foreach (var transform in patch.GetTransformsInfo(true)) { var obj = PSObject.AsPSObject(transform); // Attach the original patch path and write to the pipeline. obj.SetPropertyValue("Patch", providerPath); this.WriteObject(obj); } } } }
/// <summary> /// Applies applicable transforms in order of sequenced patches. /// </summary> internal void Apply(bool throwOnError = false) { // Need to make a copy of the database since exclusivity is required. IEnumerable<string> applicable = null; using (var copy = Copy(this.db)) { // Copy the items to a list so they are enumerated immediately and the temporary database can be closed. applicable = this.sequencer.GetApplicablePatches(copy.FilePath).Select(patch => patch.Patch).ToList(); } foreach (var path in applicable) { using (var patch = new PatchPackage(path)) { var transforms = patch.GetValidTransforms(this.db); foreach (var transform in transforms) { // GetValidTransforms does not return the patch transform so assume it too is valid. foreach (var prefix in PatchApplicator.TransformPrefixes) { var temp = Path.ChangeExtension(Path.GetTempFileName(), ".mst"); patch.ExtractTransform(prefix + transform, temp); // Apply and commit the authored transform so further transforms may apply. this.db.ApplyTransform(temp, PatchApplicator.IgnoreErrors); this.db.ApplyTransform(temp, PatchApplicator.IgnoreErrors | TransformErrors.ViewTransform); this.db.Commit(); // Attempt to delete the temporary transform. TryDelete(temp); } } } } }
private void AddTargetProductCodesFromPatch(string path) { using (var patch = new PatchPackage(path)) { foreach (var productCode in patch.GetTargetProductCodes()) { this.TargetProductCodes.Add(productCode); } } }