예제 #1
0
    /// <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");
    }
예제 #2
0
        /// <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);
                    }
                }
            }
        }
예제 #3
0
        /// <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);
                        }
                    }
                }
            }
        }
예제 #4
0
 private void AddTargetProductCodesFromPatch(string path)
 {
     using (var patch = new PatchPackage(path))
     {
         foreach (var productCode in patch.GetTargetProductCodes())
         {
             this.TargetProductCodes.Add(productCode);
         }
     }
 }