mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 04:36:39 +00:00
Merge pull request #1153 from PowerShell/quoct/oneget
Adding PackageManagement
This commit is contained in:
commit
8cfc6d6687
347 files changed
+76765
-327
No files matched your search
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
|
||||
public class CabArchiver {
|
||||
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> {
|
||||
{Constants.Features.SupportedExtensions, new[] {"cab", "msu"}},
|
||||
{Constants.Features.MagicSignatures, new[] {Constants.Signatures.Cab}}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of strings to the client advertizing features this provider supports.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetFeatures(Request request) {
|
||||
if( request == null ) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::GetFeatures' ", ArchiverName);
|
||||
foreach (var feature in _features) {
|
||||
request.Yield(feature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the Provider.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ArchiverName {
|
||||
get { return "cabfile"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the version of the Provider.
|
||||
/// </summary>
|
||||
/// <returns>The version of this provider </returns>
|
||||
public string ProviderVersion {
|
||||
get {
|
||||
return "1.0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> UnpackArchive(string localFilename, string destinationFolder, Request request) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsSupportedFile(string localFilename) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveException.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Base exception class for compression operations. Compression libraries should
|
||||
/// derive subclass exceptions with more specific error information relevent to the
|
||||
/// file format.
|
||||
/// </summary>
|
||||
public class ArchiveException : IOException
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveException with a specified error message and a reference to the
|
||||
/// inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception. If the
|
||||
/// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
|
||||
/// is raised in a catch block that handles the inner exception.</param>
|
||||
public ArchiveException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveException with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ArchiveException(string message)
|
||||
: this(message, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveException.
|
||||
/// </summary>
|
||||
public ArchiveException()
|
||||
: this(null, null)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveFileInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
#if !CORECLR
|
||||
using System.Security.Permissions;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Abstract object representing a compressed file within an archive;
|
||||
/// provides operations for getting the file properties and unpacking
|
||||
/// the file.
|
||||
/// </summary>
|
||||
public abstract class ArchiveFileInfo : FileSystemInfo
|
||||
{
|
||||
private ArchiveInfo archiveInfo;
|
||||
private string name;
|
||||
private string path;
|
||||
|
||||
private bool initialized;
|
||||
private bool exists;
|
||||
private int archiveNumber;
|
||||
private FileAttributes attributes;
|
||||
private DateTime lastWriteTime;
|
||||
private long length;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveFileInfo object representing a file within
|
||||
/// an archive in a specified path.
|
||||
/// </summary>
|
||||
/// <param name="archiveInfo">An object representing the archive
|
||||
/// containing the file.</param>
|
||||
/// <param name="filePath">The path to the file within the archive.
|
||||
/// Usually, this is a simple file name, but if the archive contains
|
||||
/// a directory structure this may include the directory.</param>
|
||||
protected ArchiveFileInfo(ArchiveInfo archiveInfo, string filePath)
|
||||
: base()
|
||||
{
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new ArgumentNullException("filePath");
|
||||
}
|
||||
|
||||
this.Archive = archiveInfo;
|
||||
|
||||
this.name = System.IO.Path.GetFileName(filePath);
|
||||
this.path = System.IO.Path.GetDirectoryName(filePath);
|
||||
|
||||
this.attributes = FileAttributes.Normal;
|
||||
this.lastWriteTime = DateTime.MinValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveFileInfo object with all parameters specified;
|
||||
/// used by subclasses when reading the metadata out of an archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The internal path and name of the file in
|
||||
/// the archive.</param>
|
||||
/// <param name="archiveNumber">The archive number where the file
|
||||
/// starts.</param>
|
||||
/// <param name="attributes">The stored attributes of the file.</param>
|
||||
/// <param name="lastWriteTime">The stored last write time of the
|
||||
/// file.</param>
|
||||
/// <param name="length">The uncompressed size of the file.</param>
|
||||
protected ArchiveFileInfo(
|
||||
string filePath,
|
||||
int archiveNumber,
|
||||
FileAttributes attributes,
|
||||
DateTime lastWriteTime,
|
||||
long length)
|
||||
: this(null, filePath)
|
||||
{
|
||||
this.exists = true;
|
||||
this.archiveNumber = archiveNumber;
|
||||
this.attributes = attributes;
|
||||
this.lastWriteTime = lastWriteTime;
|
||||
this.length = length;
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file.
|
||||
/// </summary>
|
||||
/// <value>The name of the file, not including any path.</value>
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the internal path of the file in the archive.
|
||||
/// </summary>
|
||||
/// <value>The internal path of the file in the archive, not including
|
||||
/// the file name.</value>
|
||||
public string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path to the file.
|
||||
/// </summary>
|
||||
/// <value>The full path to the file, including the full path to the
|
||||
/// archive, the internal path in the archive, and the file name.</value>
|
||||
/// <remarks>
|
||||
/// For example, the path <c>"C:\archive.cab\file.txt"</c> refers to
|
||||
/// a file "file.txt" inside the archive "archive.cab".
|
||||
/// </remarks>
|
||||
public override string FullName
|
||||
{
|
||||
get
|
||||
{
|
||||
string fullName = System.IO.Path.Combine(this.Path, this.Name);
|
||||
|
||||
if (this.Archive != null)
|
||||
{
|
||||
fullName = System.IO.Path.Combine(this.ArchiveName, fullName);
|
||||
}
|
||||
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
|
||||
public string FullNameExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
// GetFullPathInternal would have already stripped out the terminating "." if present.
|
||||
int len = FullName.Length;
|
||||
for (int i = len; --i >= 0;)
|
||||
{
|
||||
char ch = FullName[i];
|
||||
if (ch == '.')
|
||||
return FullName.Substring(i, len - i);
|
||||
if (ch == System.IO.Path.DirectorySeparatorChar || ch == System.IO.Path.AltDirectorySeparatorChar || ch == System.IO.Path.VolumeSeparatorChar)
|
||||
break;
|
||||
}
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the archive that contains this file.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ArchiveInfo instance that retrieved this file information -- this
|
||||
/// may be null if the ArchiveFileInfo object was returned directly from
|
||||
/// a stream.
|
||||
/// </value>
|
||||
public ArchiveInfo Archive
|
||||
{
|
||||
get
|
||||
{
|
||||
return (ArchiveInfo) this.archiveInfo;
|
||||
}
|
||||
|
||||
internal set
|
||||
{
|
||||
this.archiveInfo = value;
|
||||
|
||||
// protected instance members inherited from FileSystemInfo:
|
||||
this.OriginalPath = (value != null ? value.FullName : null);
|
||||
this.FullPath = this.OriginalPath;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path of the archive that contains this file.
|
||||
/// </summary>
|
||||
/// <value>The full path of the archive that contains this file.</value>
|
||||
public string ArchiveName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Archive != null ? this.Archive.FullName : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the archive where this file starts.
|
||||
/// </summary>
|
||||
/// <value>The number of the archive where this file starts.</value>
|
||||
/// <remarks>A single archive or the first archive in a chain is
|
||||
/// numbered 0.</remarks>
|
||||
public int ArchiveNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.archiveNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the file exists within the archive.
|
||||
/// </summary>
|
||||
/// <value>True if the file exists, false otherwise.</value>
|
||||
public override bool Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
this.Refresh();
|
||||
}
|
||||
|
||||
return this.exists;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the uncompressed size of the file.
|
||||
/// </summary>
|
||||
/// <value>The uncompressed size of the file in bytes.</value>
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
this.Refresh();
|
||||
}
|
||||
|
||||
return this.length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the attributes of the file.
|
||||
/// </summary>
|
||||
/// <value>The attributes of the file as stored in the archive.</value>
|
||||
public new FileAttributes Attributes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
this.Refresh();
|
||||
}
|
||||
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last modification time of the file.
|
||||
/// </summary>
|
||||
/// <value>The last modification time of the file as stored in the
|
||||
/// archive.</value>
|
||||
public new DateTime LastWriteTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!this.initialized)
|
||||
{
|
||||
this.Refresh();
|
||||
}
|
||||
|
||||
return this.lastWriteTime;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path to the file.
|
||||
/// </summary>
|
||||
/// <returns>The same as <see cref="FullName"/></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the file. NOT SUPPORTED.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Files cannot be deleted
|
||||
/// from an existing archive.</exception>
|
||||
public override void Delete()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the attributes and other cached information about the file,
|
||||
/// by re-reading the information from the archive.
|
||||
/// </summary>
|
||||
public new void Refresh()
|
||||
{
|
||||
base.Refresh();
|
||||
|
||||
if (this.Archive != null)
|
||||
{
|
||||
string filePath = System.IO.Path.Combine(this.Path, this.Name);
|
||||
ArchiveFileInfo updatedFile = this.Archive.GetFile(filePath);
|
||||
if (updatedFile == null)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"File not found in archive.", filePath);
|
||||
}
|
||||
|
||||
this.Refresh(updatedFile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the file.
|
||||
/// </summary>
|
||||
/// <param name="destFileName">The destination path where the file
|
||||
/// will be extracted.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void CopyTo(string destFileName)
|
||||
{
|
||||
this.CopyTo(destFileName, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the file, optionally overwriting any existing file.
|
||||
/// </summary>
|
||||
/// <param name="destFileName">The destination path where the file
|
||||
/// will be extracted.</param>
|
||||
/// <param name="overwrite">If true, <paramref name="destFileName"/>
|
||||
/// will be overwritten if it exists.</param>
|
||||
/// <exception cref="IOException"><paramref name="overwrite"/> is false
|
||||
/// and <paramref name="destFileName"/> exists.</exception>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void CopyTo(string destFileName, bool overwrite)
|
||||
{
|
||||
if (destFileName == null)
|
||||
{
|
||||
throw new ArgumentNullException("destFileName");
|
||||
}
|
||||
|
||||
if (!overwrite && File.Exists(destFileName))
|
||||
{
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
if (this.Archive == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this.Archive.UnpackFile(
|
||||
System.IO.Path.Combine(this.Path, this.Name), destFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the archive file for reading without actually extracting the
|
||||
/// file to disk.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A stream for reading directly from the packed file. Like any stream
|
||||
/// this should be closed/disposed as soon as it is no longer needed.
|
||||
/// </returns>
|
||||
public Stream OpenRead()
|
||||
{
|
||||
return this.Archive.OpenRead(System.IO.Path.Combine(this.Path, this.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the archive file reading text with UTF-8 encoding without
|
||||
/// actually extracting the file to disk.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A reader for reading text directly from the packed file. Like any reader
|
||||
/// this should be closed/disposed as soon as it is no longer needed.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// To open an archived text file with different encoding, use the
|
||||
/// <see cref="OpenRead" /> method and pass the returned stream to one of
|
||||
/// the <see cref="StreamReader" /> constructor overloads.
|
||||
/// </remarks>
|
||||
public StreamReader OpenText()
|
||||
{
|
||||
return this.Archive.OpenText(System.IO.Path.Combine(this.Path, this.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the information in this object with new data retrieved
|
||||
/// from an archive.
|
||||
/// </summary>
|
||||
/// <param name="newFileInfo">Fresh instance for the same file just
|
||||
/// read from the archive.</param>
|
||||
/// <remarks>
|
||||
/// Subclasses may override this method to refresh sublcass fields.
|
||||
/// However they should always call the base implementation first.
|
||||
/// </remarks>
|
||||
protected virtual void Refresh(ArchiveFileInfo newFileInfo)
|
||||
{
|
||||
this.exists = newFileInfo.exists;
|
||||
this.length = newFileInfo.length;
|
||||
this.attributes = newFileInfo.attributes;
|
||||
this.lastWriteTime = newFileInfo.lastWriteTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
+682
@@ -0,0 +1,682 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveFileStreamContext.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a basic implementation of the archive pack and unpack stream context
|
||||
/// interfaces, based on a list of archive files, a default directory, and an
|
||||
/// optional mapping from internal to external file paths.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class can also handle creating or extracting chained archive packages.
|
||||
/// </remarks>
|
||||
public class ArchiveFileStreamContext
|
||||
: IPackStreamContext, IUnpackStreamContext
|
||||
{
|
||||
private IList<string> archiveFiles;
|
||||
private string directory;
|
||||
private IDictionary<string, string> files;
|
||||
private bool extractOnlyNewerFiles;
|
||||
private bool enableOffsetOpen;
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveFileStreamContext with a archive file and
|
||||
/// no default directory or file mapping.
|
||||
/// </summary>
|
||||
/// <param name="archiveFile">The path to a archive file that will be
|
||||
/// created or extracted.</param>
|
||||
public ArchiveFileStreamContext(string archiveFile)
|
||||
: this(archiveFile, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveFileStreamContext with a archive file, default
|
||||
/// directory and mapping from internal to external file paths.
|
||||
/// </summary>
|
||||
/// <param name="archiveFile">The path to a archive file that will be
|
||||
/// created or extracted.</param>
|
||||
/// <param name="directory">The default root directory where files will be
|
||||
/// located, optional.</param>
|
||||
/// <param name="files">A mapping from internal file paths to external file
|
||||
/// paths, optional.</param>
|
||||
/// <remarks>
|
||||
/// If the mapping is not null and a file is not included in the mapping,
|
||||
/// the file will be skipped.
|
||||
/// <para>If the external path in the mapping is a simple file name or
|
||||
/// relative file path, it will be concatenated onto the default directory,
|
||||
/// if one was specified.</para>
|
||||
/// <para>For more about how the default directory and files mapping are
|
||||
/// used, see <see cref="OpenFileReadStream"/> and
|
||||
/// <see cref="OpenFileWriteStream"/>.</para>
|
||||
/// </remarks>
|
||||
public ArchiveFileStreamContext(
|
||||
string archiveFile,
|
||||
string directory,
|
||||
IDictionary<string, string> files)
|
||||
: this(new string[] { archiveFile }, directory, files)
|
||||
{
|
||||
if (archiveFile == null)
|
||||
{
|
||||
throw new ArgumentNullException("archiveFile");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveFileStreamContext with a list of archive files,
|
||||
/// a default directory and a mapping from internal to external file paths.
|
||||
/// </summary>
|
||||
/// <param name="archiveFiles">A list of paths to archive files that will be
|
||||
/// created or extracted.</param>
|
||||
/// <param name="directory">The default root directory where files will be
|
||||
/// located, optional.</param>
|
||||
/// <param name="files">A mapping from internal file paths to external file
|
||||
/// paths, optional.</param>
|
||||
/// <remarks>
|
||||
/// When creating chained archives, the <paramref name="archiveFiles"/> list
|
||||
/// should include at least enough archives to handle the entire set of
|
||||
/// input files, based on the maximum archive size that is passed to the
|
||||
/// <see cref="CompressionEngine"/>.<see
|
||||
/// cref="CompressionEngine.Pack(IPackStreamContext,IEnumerable<string>,long)"/>.
|
||||
/// <para>If the mapping is not null and a file is not included in the mapping,
|
||||
/// the file will be skipped.</para>
|
||||
/// <para>If the external path in the mapping is a simple file name or
|
||||
/// relative file path, it will be concatenated onto the default directory,
|
||||
/// if one was specified.</para>
|
||||
/// <para>For more about how the default directory and files mapping are used,
|
||||
/// see <see cref="OpenFileReadStream"/> and
|
||||
/// <see cref="OpenFileWriteStream"/>.</para>
|
||||
/// </remarks>
|
||||
public ArchiveFileStreamContext(
|
||||
IList<string> archiveFiles,
|
||||
string directory,
|
||||
IDictionary<string, string> files)
|
||||
{
|
||||
if (archiveFiles == null || archiveFiles.Count == 0)
|
||||
{
|
||||
throw new ArgumentNullException("archiveFiles");
|
||||
}
|
||||
|
||||
this.archiveFiles = archiveFiles;
|
||||
this.directory = directory;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of archive files that are created or extracted.
|
||||
/// </summary>
|
||||
/// <value>The list of archive files that are created or extracted.</value>
|
||||
public IList<string> ArchiveFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.archiveFiles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default root directory where files are located.
|
||||
/// </summary>
|
||||
/// <value>The default root directory where files are located.</value>
|
||||
/// <remarks>
|
||||
/// For details about how the default directory is used,
|
||||
/// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
|
||||
/// </remarks>
|
||||
public string Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.directory;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mapping from internal file paths to external file paths.
|
||||
/// </summary>
|
||||
/// <value>A mapping from internal file paths to external file paths.</value>
|
||||
/// <remarks>
|
||||
/// For details about how the files mapping is used,
|
||||
/// see <see cref="OpenFileReadStream"/> and <see cref="OpenFileWriteStream"/>.
|
||||
/// </remarks>
|
||||
public IDictionary<string, string> Files
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.files;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that can prevent extracted files from overwriting
|
||||
/// newer files that already exist.
|
||||
/// </summary>
|
||||
/// <value>True to prevent overwriting newer files that already exist
|
||||
/// during extraction; false to always extract from the archive regardless
|
||||
/// of existing files.</value>
|
||||
public bool ExtractOnlyNewerFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.extractOnlyNewerFiles;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.extractOnlyNewerFiles = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that enables creating or extracting an archive
|
||||
/// at an offset within an existing file. (This is typically used to open
|
||||
/// archive-based self-extracting packages.)
|
||||
/// </summary>
|
||||
/// <value>True to search an existing package file for an archive offset
|
||||
/// or the end of the file;/ false to always create or open a plain
|
||||
/// archive file.</value>
|
||||
public bool EnableOffsetOpen
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.enableOffsetOpen;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.enableOffsetOpen = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPackStreamContext Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the archive with a specified number.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive within
|
||||
/// the chain.</param>
|
||||
/// <returns>The name of the requested archive. May be an empty string
|
||||
/// for non-chained archives, but may never be null.</returns>
|
||||
/// <remarks>This method returns the file name of the archive from the
|
||||
/// <see cref="archiveFiles"/> list with the specified index, or an empty
|
||||
/// string if the archive number is outside the bounds of the list. The
|
||||
/// file name should not include any directory path.</remarks>
|
||||
public virtual string GetArchiveName(int archiveNumber)
|
||||
{
|
||||
if (archiveNumber < this.archiveFiles.Count)
|
||||
{
|
||||
return Path.GetFileName(this.archiveFiles[archiveNumber]);
|
||||
}
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream for writing an archive.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive within
|
||||
/// the chain.</param>
|
||||
/// <param name="archiveName">The name of the archive that was returned
|
||||
/// by <see cref="GetArchiveName"/>.</param>
|
||||
/// <param name="truncate">True if the stream should be truncated when
|
||||
/// opened (if it already exists); false if an existing stream is being
|
||||
/// re-opened for writing additional data.</param>
|
||||
/// <param name="compressionEngine">Instance of the compression engine
|
||||
/// doing the operations.</param>
|
||||
/// <returns>A writable Stream where the compressed archive bytes will be
|
||||
/// written, or null to cancel the archive creation.</returns>
|
||||
/// <remarks>
|
||||
/// This method opens the file from the <see cref="ArchiveFiles"/> list
|
||||
/// with the specified index. If the archive number is outside the bounds
|
||||
/// of the list, this method returns null.
|
||||
/// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method
|
||||
/// will seek to the start of any existing archive in the file, or to the
|
||||
/// end of the file if the existing file is not an archive.</para>
|
||||
/// </remarks>
|
||||
public virtual Stream OpenArchiveWriteStream(
|
||||
int archiveNumber,
|
||||
string archiveName,
|
||||
bool truncate,
|
||||
CompressionEngine compressionEngine)
|
||||
{
|
||||
if (archiveNumber >= this.archiveFiles.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(archiveName))
|
||||
{
|
||||
throw new ArgumentNullException("archiveName");
|
||||
}
|
||||
|
||||
// All archives must be in the same directory,
|
||||
// so always use the directory from the first archive.
|
||||
string archiveFile = Path.Combine(
|
||||
Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
|
||||
Stream stream = File.Open(
|
||||
archiveFile,
|
||||
(truncate ? FileMode.OpenOrCreate : FileMode.Open),
|
||||
FileAccess.ReadWrite);
|
||||
|
||||
if (this.enableOffsetOpen)
|
||||
{
|
||||
if (compressionEngine == null) {
|
||||
throw new ArgumentNullException("compressionEngine");
|
||||
}
|
||||
|
||||
long offset = compressionEngine.FindArchiveOffset(
|
||||
new DuplicateStream(stream));
|
||||
|
||||
// If this is not an archive file, append the archive to it.
|
||||
if (offset < 0)
|
||||
{
|
||||
offset = stream.Length;
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
stream = new OffsetStream(stream, offset);
|
||||
}
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
if (truncate)
|
||||
{
|
||||
// Truncate the stream, in case a larger old archive starts here.
|
||||
stream.SetLength(0);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an archive package was written.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive within
|
||||
/// the chain.</param>
|
||||
/// <param name="archiveName">The name of the archive that was previously
|
||||
/// returned by <see cref="GetArchiveName"/>.</param>
|
||||
/// <param name="stream">A stream that was previously returned by
|
||||
/// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
|
||||
public virtual void CloseArchiveWriteStream(
|
||||
int archiveNumber,
|
||||
string archiveName,
|
||||
Stream stream)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
stream.Dispose();
|
||||
#else
|
||||
stream.Close();
|
||||
#endif
|
||||
|
||||
FileStream fileStream = stream as FileStream;
|
||||
if (fileStream != null)
|
||||
{
|
||||
string streamFile = fileStream.Name;
|
||||
if (!string.IsNullOrWhiteSpace(archiveName) &&
|
||||
archiveName != Path.GetFileName(streamFile))
|
||||
{
|
||||
string archiveFile = Path.Combine(
|
||||
Path.GetDirectoryName(this.archiveFiles[0]), archiveName);
|
||||
if (File.Exists(archiveFile))
|
||||
{
|
||||
File.Delete(archiveFile);
|
||||
}
|
||||
File.Move(streamFile, archiveFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream to read a file that is to be included in an archive.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive.</param>
|
||||
/// <param name="attributes">The returned attributes of the opened file,
|
||||
/// to be stored in the archive.</param>
|
||||
/// <param name="lastWriteTime">The returned last-modified time of the
|
||||
/// opened file, to be stored in the archive.</param>
|
||||
/// <returns>A readable Stream where the file bytes will be read from
|
||||
/// before they are compressed, or null to skip inclusion of the file and
|
||||
/// continue to the next file.</returns>
|
||||
/// <remarks>
|
||||
/// This method opens a file using the following logic:
|
||||
/// <list>
|
||||
/// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
|
||||
/// are both null, the path is treated as relative to the current directory,
|
||||
/// and that file is opened.</item>
|
||||
/// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
|
||||
/// mapping is null, the path is treated as relative to that directory, and
|
||||
/// that file is opened.</item>
|
||||
/// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
|
||||
/// mapping is not null, the path parameter is used as a key into the mapping,
|
||||
/// and the resulting value is the file path that is opened, relative to the
|
||||
/// current directory (or it may be an absolute path). If no mapping exists,
|
||||
/// the file is skipped.</item>
|
||||
/// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
|
||||
/// mapping are specified, the path parameter is used as a key into the
|
||||
/// mapping, and the resulting value is the file path that is opened, relative
|
||||
/// to the specified directory (or it may be an absolute path). If no mapping
|
||||
/// exists, the file is skipped.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public virtual Stream OpenFileReadStream(
|
||||
string path, out FileAttributes attributes, out DateTime lastWriteTime)
|
||||
{
|
||||
string filePath = this.TranslateFilePath(path);
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
attributes = FileAttributes.Normal;
|
||||
lastWriteTime = DateTime.Now;
|
||||
return null;
|
||||
}
|
||||
|
||||
attributes = File.GetAttributes(filePath);
|
||||
lastWriteTime = File.GetLastWriteTime(filePath);
|
||||
return File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream that has been used to read a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive; the same as
|
||||
/// the path provided when the stream was opened.</param>
|
||||
/// <param name="stream">A stream that was previously returned by
|
||||
/// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
|
||||
public virtual void CloseFileReadStream(string path, Stream stream)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
stream.Dispose();
|
||||
#else
|
||||
stream.Close();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets extended parameter information specific to the compression format
|
||||
/// being used.
|
||||
/// </summary>
|
||||
/// <param name="optionName">Name of the option being requested.</param>
|
||||
/// <param name="parameters">Parameters for the option; for per-file options,
|
||||
/// the first parameter is typically the internal file path.</param>
|
||||
/// <returns>Option value, or null to use the default behavior.</returns>
|
||||
/// <remarks>
|
||||
/// This implementation does not handle any options. Subclasses may override
|
||||
/// this method to allow for non-default behavior.
|
||||
/// </remarks>
|
||||
public virtual object GetOption(string optionName, object[] parameters)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IUnpackStreamContext Members
|
||||
|
||||
/// <summary>
|
||||
/// Opens the archive stream for reading.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The zero-based index of the archive to
|
||||
/// open.</param>
|
||||
/// <param name="archiveName">The name of the archive being opened.</param>
|
||||
/// <param name="compressionEngine">Instance of the compression engine
|
||||
/// doing the operations.</param>
|
||||
/// <returns>A stream from which archive bytes are read, or null to cancel
|
||||
/// extraction of the archive.</returns>
|
||||
/// <remarks>
|
||||
/// This method opens the file from the <see cref="ArchiveFiles"/> list with
|
||||
/// the specified index. If the archive number is outside the bounds of the
|
||||
/// list, this method returns null.
|
||||
/// <para>If the <see cref="EnableOffsetOpen"/> flag is set, this method will
|
||||
/// seek to the start of any existing archive in the file, or to the end of
|
||||
/// the file if the existing file is not an archive.</para>
|
||||
/// </remarks>
|
||||
public virtual Stream OpenArchiveReadStream(
|
||||
int archiveNumber, string archiveName, CompressionEngine compressionEngine)
|
||||
{
|
||||
if (archiveNumber >= this.archiveFiles.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string archiveFile = this.archiveFiles[archiveNumber];
|
||||
Stream stream = File.Open(
|
||||
archiveFile, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
if (this.enableOffsetOpen)
|
||||
{
|
||||
if (compressionEngine == null) {
|
||||
throw new ArgumentNullException("compressionEngine");
|
||||
}
|
||||
long offset = compressionEngine.FindArchiveOffset(
|
||||
new DuplicateStream(stream));
|
||||
if (offset > 0)
|
||||
{
|
||||
stream = new OffsetStream(stream, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an archive was read.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The archive number of the stream
|
||||
/// to close.</param>
|
||||
/// <param name="archiveName">The name of the archive being closed.</param>
|
||||
/// <param name="stream">The stream that was previously returned by
|
||||
/// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
|
||||
public virtual void CloseArchiveReadStream(
|
||||
int archiveNumber, string archiveName, Stream stream)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
stream.Dispose();
|
||||
#else
|
||||
stream.Close();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream for writing extracted file bytes.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive.</param>
|
||||
/// <param name="fileSize">The uncompressed size of the file to be
|
||||
/// extracted.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file to be
|
||||
/// extracted.</param>
|
||||
/// <returns>A stream where extracted file bytes are to be written, or null
|
||||
/// to skip extraction of the file and continue to the next file.</returns>
|
||||
/// <remarks>
|
||||
/// This method opens a file using the following logic:
|
||||
/// <list>
|
||||
/// <item>If the <see cref="Directory"/> and the <see cref="Files"/> mapping
|
||||
/// are both null, the path is treated as relative to the current directory,
|
||||
/// and that file is opened.</item>
|
||||
/// <item>If the <see cref="Directory"/> is not null but the <see cref="Files"/>
|
||||
/// mapping is null, the path is treated as relative to that directory, and
|
||||
/// that file is opened.</item>
|
||||
/// <item>If the <see cref="Directory"/> is null but the <see cref="Files"/>
|
||||
/// mapping is not null, the path parameter is used as a key into the mapping,
|
||||
/// and the resulting value is the file path that is opened, relative to the
|
||||
/// current directory (or it may be an absolute path). If no mapping exists,
|
||||
/// the file is skipped.</item>
|
||||
/// <item>If both the <see cref="Directory"/> and the <see cref="Files"/>
|
||||
/// mapping are specified, the path parameter is used as a key into the
|
||||
/// mapping, and the resulting value is the file path that is opened,
|
||||
/// relative to the specified directory (or it may be an absolute path).
|
||||
/// If no mapping exists, the file is skipped.</item>
|
||||
/// </list>
|
||||
/// <para>If the <see cref="ExtractOnlyNewerFiles"/> flag is set, the file
|
||||
/// is skipped if a file currently exists in the same path with an equal
|
||||
/// or newer write time.</para>
|
||||
/// </remarks>
|
||||
public virtual Stream OpenFileWriteStream(
|
||||
string path,
|
||||
long fileSize,
|
||||
DateTime lastWriteTime)
|
||||
{
|
||||
string filePath = this.TranslateFilePath(path);
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInfo file = new FileInfo(filePath);
|
||||
if (file.Exists)
|
||||
{
|
||||
if (this.extractOnlyNewerFiles && lastWriteTime != DateTime.MinValue)
|
||||
{
|
||||
if (file.LastWriteTime >= lastWriteTime)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear attributes that will prevent overwriting the file.
|
||||
// (The final attributes will be set after the file is unpacked.)
|
||||
FileAttributes attributesToClear =
|
||||
FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System;
|
||||
if ((file.Attributes & attributesToClear) != 0)
|
||||
{
|
||||
file.Attributes &= ~attributesToClear;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.Directory.Exists)
|
||||
{
|
||||
file.Directory.Create();
|
||||
}
|
||||
|
||||
return File.Open(
|
||||
filePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an extracted file was written.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive.</param>
|
||||
/// <param name="stream">The stream that was previously returned by
|
||||
/// <see cref="OpenFileWriteStream"/> and is now ready to be closed.</param>
|
||||
/// <param name="attributes">The attributes of the extracted file.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file.</param>
|
||||
/// <remarks>
|
||||
/// After closing the extracted file stream, this method applies the date
|
||||
/// and attributes to that file.
|
||||
/// </remarks>
|
||||
public virtual void CloseFileWriteStream(
|
||||
string path,
|
||||
Stream stream,
|
||||
FileAttributes attributes,
|
||||
DateTime lastWriteTime)
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
stream.Dispose();
|
||||
#else
|
||||
stream.Close();
|
||||
#endif
|
||||
}
|
||||
|
||||
string filePath = this.TranslateFilePath(path);
|
||||
if (filePath != null)
|
||||
{
|
||||
FileInfo file = new FileInfo(filePath);
|
||||
|
||||
if (lastWriteTime != DateTime.MinValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.LastWriteTime = lastWriteTime;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
file.Attributes = attributes;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private utility methods
|
||||
|
||||
/// <summary>
|
||||
/// Translates an internal file path to an external file path using the
|
||||
/// <see cref="Directory"/> and the <see cref="Files"/> mapping, according to
|
||||
/// rules documented in <see cref="OpenFileReadStream"/> and
|
||||
/// <see cref="OpenFileWriteStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file with the archive.</param>
|
||||
/// <returns>The external path of the file, or null if there is no
|
||||
/// valid translation.</returns>
|
||||
private string TranslateFilePath(string path)
|
||||
{
|
||||
string filePath;
|
||||
if (this.files != null)
|
||||
{
|
||||
filePath = this.files[path];
|
||||
}
|
||||
else
|
||||
{
|
||||
filePath = path;
|
||||
}
|
||||
|
||||
if (filePath != null)
|
||||
{
|
||||
if (this.directory != null)
|
||||
{
|
||||
filePath = Path.Combine(this.directory, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,774 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract object representing a compressed archive on disk;
|
||||
/// provides access to file-based operations on the archive.
|
||||
/// </summary>
|
||||
public abstract class ArchiveInfo : FileSystemInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveInfo object representing an archive in a
|
||||
/// specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the archive. When creating an archive,
|
||||
/// this file does not necessarily exist yet.</param>
|
||||
protected ArchiveInfo(string path)
|
||||
: base()
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
// protected instance members inherited from FileSystemInfo:
|
||||
this.OriginalPath = path;
|
||||
this.FullPath = Path.GetFullPath(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory that contains the archive.
|
||||
/// </summary>
|
||||
/// <value>A DirectoryInfo object representing the parent directory of the
|
||||
/// archive.</value>
|
||||
public DirectoryInfo Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
return new DirectoryInfo(Path.GetDirectoryName(this.FullName));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path of the directory that contains the archive.
|
||||
/// </summary>
|
||||
/// <value>The full path of the directory that contains the archive.</value>
|
||||
public string DirectoryName
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetDirectoryName(this.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the archive.
|
||||
/// </summary>
|
||||
/// <value>The size of the archive in bytes.</value>
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return new FileInfo(this.FullName).Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name of the archive.
|
||||
/// </summary>
|
||||
/// <value>The file name of the archive, not including any path.</value>
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetFileName(this.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the archive exists.
|
||||
/// </summary>
|
||||
/// <value>True if the archive exists; else false.</value>
|
||||
public override bool Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
return File.Exists(this.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path of the archive.
|
||||
/// </summary>
|
||||
/// <returns>The full path of the archive.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the archive.
|
||||
/// </summary>
|
||||
public override void Delete()
|
||||
{
|
||||
File.Delete(this.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies an existing archive to another location.
|
||||
/// </summary>
|
||||
/// <param name="destFileName">The destination file path.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void CopyTo(string destFileName)
|
||||
{
|
||||
File.Copy(this.FullName, destFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies an existing archive to another location, optionally
|
||||
/// overwriting the destination file.
|
||||
/// </summary>
|
||||
/// <param name="destFileName">The destination file path.</param>
|
||||
/// <param name="overwrite">If true, the destination file will be
|
||||
/// overwritten if it exists.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void CopyTo(string destFileName, bool overwrite)
|
||||
{
|
||||
File.Copy(this.FullName, destFileName, overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves an existing archive to another location.
|
||||
/// </summary>
|
||||
/// <param name="destFileName">The destination file path.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void MoveTo(string destFileName)
|
||||
{
|
||||
File.Move(this.FullName, destFileName);
|
||||
this.FullPath = Path.GetFullPath(destFileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the archive contains a valid archive header.
|
||||
/// </summary>
|
||||
/// <returns>True if the file is a valid archive; false otherwise.</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
using (Stream stream = File.OpenRead(this.FullName))
|
||||
{
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
return compressionEngine.FindArchiveOffset(stream) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the files contained in the archive.
|
||||
/// </summary>
|
||||
/// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
|
||||
/// containing information about a file in the archive.</returns>
|
||||
public IList<ArchiveFileInfo> GetFiles()
|
||||
{
|
||||
return this.InternalGetFiles((Predicate<string>)null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the certain files contained in the archive file.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">The search string, such as
|
||||
/// "*.txt".</param>
|
||||
/// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each containing
|
||||
/// information about a file in the archive.</returns>
|
||||
public IList<ArchiveFileInfo> GetFiles(string searchPattern)
|
||||
{
|
||||
if (searchPattern == null)
|
||||
{
|
||||
throw new ArgumentNullException("searchPattern");
|
||||
}
|
||||
|
||||
string regexPattern = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"^{0}$",
|
||||
Regex.Escape(searchPattern).Replace("\\*", ".*").Replace("\\?", "."));
|
||||
Regex regex = new Regex(
|
||||
regexPattern,
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
return this.InternalGetFiles(
|
||||
delegate(string match)
|
||||
{
|
||||
return regex.IsMatch(match);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all files from an archive to a destination directory.
|
||||
/// </summary>
|
||||
/// <param name="destDirectory">Directory where the files are to be
|
||||
/// extracted.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void Unpack(string destDirectory)
|
||||
{
|
||||
this.Unpack(destDirectory, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all files from an archive to a destination directory,
|
||||
/// optionally extracting only newer files.
|
||||
/// </summary>
|
||||
/// <param name="destDirectory">Directory where the files are to be
|
||||
/// extracted.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress
|
||||
/// information; this may be null if progress is not desired.</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void Unpack(
|
||||
string destDirectory,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
compressionEngine.Progress += progressHandler;
|
||||
ArchiveFileStreamContext streamContext =
|
||||
new ArchiveFileStreamContext(this.FullName, destDirectory, null);
|
||||
streamContext.EnableOffsetOpen = true;
|
||||
compressionEngine.Unpack(streamContext, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a single file from the archive.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file in the archive. Also
|
||||
/// includes the internal path of the file, if any. File name matching
|
||||
/// is case-insensitive.</param>
|
||||
/// <param name="destFileName">The path where the file is to be
|
||||
/// extracted on disk.</param>
|
||||
/// <remarks>If <paramref name="destFileName"/> already exists,
|
||||
/// it will be overwritten.</remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void UnpackFile(string fileName, string destFileName)
|
||||
{
|
||||
if (fileName == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileName");
|
||||
}
|
||||
|
||||
if (destFileName == null)
|
||||
{
|
||||
throw new ArgumentNullException("destFileName");
|
||||
}
|
||||
|
||||
this.UnpackFiles(
|
||||
new string[] { fileName },
|
||||
null,
|
||||
new string[] { destFileName });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts multiple files from the archive.
|
||||
/// </summary>
|
||||
/// <param name="fileNames">The names of the files in the archive.
|
||||
/// Each name includes the internal path of the file, if any. File name
|
||||
/// matching is case-insensitive.</param>
|
||||
/// <param name="destDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory for any relative paths in
|
||||
/// <paramref name="destFileNames"/>.</param>
|
||||
/// <param name="destFileNames">The paths where the files are to be
|
||||
/// extracted on disk. If this parameter is null, the files will be
|
||||
/// extracted with the names from the archive.</param>
|
||||
/// <remarks>
|
||||
/// If any extracted files already exist on disk, they will be overwritten.
|
||||
/// <p>The <paramref name="destDirectory"/> and
|
||||
/// <paramref name="destFileNames"/> parameters cannot both be null.</p>
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void UnpackFiles(
|
||||
IList<string> fileNames,
|
||||
string destDirectory,
|
||||
IList<string> destFileNames)
|
||||
{
|
||||
this.UnpackFiles(fileNames, destDirectory, destFileNames, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts multiple files from the archive, optionally extracting
|
||||
/// only newer files.
|
||||
/// </summary>
|
||||
/// <param name="fileNames">The names of the files in the archive.
|
||||
/// Each name includes the internal path of the file, if any. File name
|
||||
/// matching is case-insensitive.</param>
|
||||
/// <param name="destDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory for any relative paths in
|
||||
/// <paramref name="destFileNames"/>.</param>
|
||||
/// <param name="destFileNames">The paths where the files are to be
|
||||
/// extracted on disk. If this parameter is null, the files will be
|
||||
/// extracted with the names from the archive.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress information;
|
||||
/// this may be null if progress is not desired.</param>
|
||||
/// <remarks>
|
||||
/// If any extracted files already exist on disk, they will be overwritten.
|
||||
/// <p>The <paramref name="destDirectory"/> and
|
||||
/// <paramref name="destFileNames"/> parameters cannot both be null.</p>
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void UnpackFiles(
|
||||
IList<string> fileNames,
|
||||
string destDirectory,
|
||||
IList<string> destFileNames,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
if (fileNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileNames");
|
||||
}
|
||||
|
||||
if (destFileNames == null)
|
||||
{
|
||||
if (destDirectory == null)
|
||||
{
|
||||
throw new ArgumentNullException("destFileNames");
|
||||
}
|
||||
|
||||
destFileNames = fileNames;
|
||||
}
|
||||
|
||||
if (destFileNames.Count != fileNames.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("destFileNames");
|
||||
}
|
||||
|
||||
IDictionary<string, string> files =
|
||||
ArchiveInfo.CreateStringDictionary(fileNames, destFileNames);
|
||||
this.UnpackFileSet(files, destDirectory, progressHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts multiple files from the archive.
|
||||
/// </summary>
|
||||
/// <param name="fileNames">A mapping from internal file paths to
|
||||
/// external file paths. Case-senstivity when matching internal paths
|
||||
/// depends on the IDictionary implementation.</param>
|
||||
/// <param name="destDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory for any relative external paths
|
||||
/// in <paramref name="fileNames"/>.</param>
|
||||
/// <remarks>
|
||||
/// If any extracted files already exist on disk, they will be overwritten.
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void UnpackFileSet(
|
||||
IDictionary<string, string> fileNames,
|
||||
string destDirectory)
|
||||
{
|
||||
this.UnpackFileSet(fileNames, destDirectory, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts multiple files from the archive.
|
||||
/// </summary>
|
||||
/// <param name="fileNames">A mapping from internal file paths to
|
||||
/// external file paths. Case-senstivity when matching internal
|
||||
/// paths depends on the IDictionary implementation.</param>
|
||||
/// <param name="destDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory for any relative external
|
||||
/// paths in <paramref name="fileNames"/>.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress
|
||||
/// information; this may be null if progress is not desired.</param>
|
||||
/// <remarks>
|
||||
/// If any extracted files already exist on disk, they will be overwritten.
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "dest")]
|
||||
public void UnpackFileSet(
|
||||
IDictionary<string, string> fileNames,
|
||||
string destDirectory,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
if (fileNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileNames");
|
||||
}
|
||||
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
compressionEngine.Progress += progressHandler;
|
||||
ArchiveFileStreamContext streamContext =
|
||||
new ArchiveFileStreamContext(this.FullName, destDirectory, fileNames);
|
||||
streamContext.EnableOffsetOpen = true;
|
||||
compressionEngine.Unpack(
|
||||
streamContext,
|
||||
delegate(string match)
|
||||
{
|
||||
return fileNames.ContainsKey(match);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file inside the archive for reading without actually
|
||||
/// extracting the file to disk.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file in the archive. Also
|
||||
/// includes the internal path of the file, if any. File name matching
|
||||
/// is case-insensitive.</param>
|
||||
/// <returns>
|
||||
/// A stream for reading directly from the packed file. Like any stream
|
||||
/// this should be closed/disposed as soon as it is no longer needed.
|
||||
/// </returns>
|
||||
public Stream OpenRead(string fileName)
|
||||
{
|
||||
Stream archiveStream = File.OpenRead(this.FullName);
|
||||
CompressionEngine compressionEngine = this.CreateCompressionEngine();
|
||||
Stream fileStream = compressionEngine.Unpack(archiveStream, fileName);
|
||||
|
||||
// Attach the archiveStream and compressionEngine to the
|
||||
// fileStream so they get disposed when the fileStream is disposed.
|
||||
return new CargoStream(fileStream, archiveStream, compressionEngine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file inside the archive for reading text with UTF-8 encoding
|
||||
/// without actually extracting the file to disk.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file in the archive. Also
|
||||
/// includes the internal path of the file, if any. File name matching
|
||||
/// is case-insensitive.</param>
|
||||
/// <returns>
|
||||
/// A reader for reading text directly from the packed file. Like any reader
|
||||
/// this should be closed/disposed as soon as it is no longer needed.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// To open an archived text file with different encoding, use the
|
||||
/// <see cref="OpenRead" /> method and pass the returned stream to one of
|
||||
/// the <see cref="StreamReader" /> constructor overloads.
|
||||
/// </remarks>
|
||||
public StreamReader OpenText(string fileName)
|
||||
{
|
||||
return new StreamReader(this.OpenRead(fileName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses all files in a directory into the archive.
|
||||
/// Does not include subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">The directory containing the
|
||||
/// files to be included.</param>
|
||||
/// <remarks>
|
||||
/// Uses maximum compression level.
|
||||
/// </remarks>
|
||||
public void Pack(string sourceDirectory)
|
||||
{
|
||||
this.Pack(sourceDirectory, false, CompressionLevel.Max, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses all files in a directory into the archive, optionally
|
||||
/// including subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">This is the root directory
|
||||
/// for to pack all files.</param>
|
||||
/// <param name="includeSubdirectories">If true, recursively include
|
||||
/// files in subdirectories.</param>
|
||||
/// <param name="compLevel">The compression level used when creating
|
||||
/// the archive.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress information;
|
||||
/// this may be null if progress is not desired.</param>
|
||||
/// <remarks>
|
||||
/// The files are stored in the archive using their relative file paths in
|
||||
/// the directory tree, if supported by the archive file format.
|
||||
/// </remarks>
|
||||
public void Pack(
|
||||
string sourceDirectory,
|
||||
bool includeSubdirectories,
|
||||
CompressionLevel compLevel,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
IList<string> files = this.GetRelativeFilePathsInDirectoryTree(
|
||||
sourceDirectory, includeSubdirectories);
|
||||
this.PackFiles(sourceDirectory, files, files, compLevel, progressHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses files into the archive, specifying the names used to
|
||||
/// store the files in the archive.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">This parameter may be null, but
|
||||
/// if specified it is the root directory
|
||||
/// for any relative paths in <paramref name="sourceFileNames"/>.</param>
|
||||
/// <param name="sourceFileNames">The list of files to be included in
|
||||
/// the archive.</param>
|
||||
/// <param name="fileNames">The names of the files as they are stored
|
||||
/// in the archive. Each name
|
||||
/// includes the internal path of the file, if any. This parameter may
|
||||
/// be null, in which case the files are stored in the archive with their
|
||||
/// source file names and no path information.</param>
|
||||
/// <remarks>
|
||||
/// Uses maximum compression level.
|
||||
/// <p>Duplicate items in the <paramref name="fileNames"/> array will cause
|
||||
/// an <see cref="ArchiveException"/>.</p>
|
||||
/// </remarks>
|
||||
public void PackFiles(
|
||||
string sourceDirectory,
|
||||
IList<string> sourceFileNames,
|
||||
IList<string> fileNames)
|
||||
{
|
||||
this.PackFiles(
|
||||
sourceDirectory,
|
||||
sourceFileNames,
|
||||
fileNames,
|
||||
CompressionLevel.Max,
|
||||
null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses files into the archive, specifying the names used to
|
||||
/// store the files in the archive.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory
|
||||
/// for any relative paths in <paramref name="sourceFileNames"/>.</param>
|
||||
/// <param name="sourceFileNames">The list of files to be included in
|
||||
/// the archive.</param>
|
||||
/// <param name="fileNames">The names of the files as they are stored in
|
||||
/// the archive. Each name includes the internal path of the file, if any.
|
||||
/// This parameter may be null, in which case the files are stored in the
|
||||
/// archive with their source file names and no path information.</param>
|
||||
/// <param name="compLevel">The compression level used when creating the
|
||||
/// archive.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress information;
|
||||
/// this may be null if progress is not desired.</param>
|
||||
/// <remarks>
|
||||
/// Duplicate items in the <paramref name="fileNames"/> array will cause
|
||||
/// an <see cref="ArchiveException"/>.
|
||||
/// </remarks>
|
||||
public void PackFiles(
|
||||
string sourceDirectory,
|
||||
IList<string> sourceFileNames,
|
||||
IList<string> fileNames,
|
||||
CompressionLevel compLevel,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
if (sourceFileNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("sourceFileNames");
|
||||
}
|
||||
|
||||
if (fileNames == null)
|
||||
{
|
||||
string[] fileNamesArray = new string[sourceFileNames.Count];
|
||||
for (int i = 0; i < sourceFileNames.Count; i++)
|
||||
{
|
||||
fileNamesArray[i] = Path.GetFileName(sourceFileNames[i]);
|
||||
}
|
||||
|
||||
fileNames = fileNamesArray;
|
||||
}
|
||||
else if (fileNames.Count != sourceFileNames.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("fileNames");
|
||||
}
|
||||
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
compressionEngine.Progress += progressHandler;
|
||||
IDictionary<string, string> contextFiles =
|
||||
ArchiveInfo.CreateStringDictionary(fileNames, sourceFileNames);
|
||||
ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
|
||||
this.FullName, sourceDirectory, contextFiles);
|
||||
streamContext.EnableOffsetOpen = true;
|
||||
compressionEngine.CompressionLevel = compLevel;
|
||||
compressionEngine.Pack(streamContext, fileNames);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses files into the archive, specifying the names used
|
||||
/// to store the files in the archive.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory
|
||||
/// for any relative paths in <paramref name="fileNames"/>.</param>
|
||||
/// <param name="fileNames">A mapping from internal file paths to
|
||||
/// external file paths.</param>
|
||||
/// <remarks>
|
||||
/// Uses maximum compression level.
|
||||
/// </remarks>
|
||||
public void PackFileSet(
|
||||
string sourceDirectory,
|
||||
IDictionary<string, string> fileNames)
|
||||
{
|
||||
this.PackFileSet(sourceDirectory, fileNames, CompressionLevel.Max, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresses files into the archive, specifying the names used to
|
||||
/// store the files in the archive.
|
||||
/// </summary>
|
||||
/// <param name="sourceDirectory">This parameter may be null, but if
|
||||
/// specified it is the root directory
|
||||
/// for any relative paths in <paramref name="fileNames"/>.</param>
|
||||
/// <param name="fileNames">A mapping from internal file paths to
|
||||
/// external file paths.</param>
|
||||
/// <param name="compLevel">The compression level used when creating
|
||||
/// the archive.</param>
|
||||
/// <param name="progressHandler">Handler for receiving progress information;
|
||||
/// this may be null if progress is not desired.</param>
|
||||
public void PackFileSet(
|
||||
string sourceDirectory,
|
||||
IDictionary<string, string> fileNames,
|
||||
CompressionLevel compLevel,
|
||||
EventHandler<ArchiveProgressEventArgs> progressHandler)
|
||||
{
|
||||
if (fileNames == null)
|
||||
{
|
||||
throw new ArgumentNullException("fileNames");
|
||||
}
|
||||
|
||||
string[] fileNamesArray = new string[fileNames.Count];
|
||||
fileNames.Keys.CopyTo(fileNamesArray, 0);
|
||||
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
compressionEngine.Progress += progressHandler;
|
||||
ArchiveFileStreamContext streamContext = new ArchiveFileStreamContext(
|
||||
this.FullName, sourceDirectory, fileNames);
|
||||
streamContext.EnableOffsetOpen = true;
|
||||
compressionEngine.CompressionLevel = compLevel;
|
||||
compressionEngine.Pack(streamContext, fileNamesArray);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a directory, gets the relative paths of all files in the
|
||||
/// directory, optionally including all subdirectories.
|
||||
/// </summary>
|
||||
/// <param name="dir">The directory to search.</param>
|
||||
/// <param name="includeSubdirectories">True to include subdirectories
|
||||
/// in the search.</param>
|
||||
/// <returns>A list of file paths relative to the directory.</returns>
|
||||
internal IList<string> GetRelativeFilePathsInDirectoryTree(
|
||||
string dir, bool includeSubdirectories)
|
||||
{
|
||||
IList<string> fileList = new List<string>();
|
||||
this.RecursiveGetRelativeFilePathsInDirectoryTree(
|
||||
dir, String.Empty, includeSubdirectories, fileList);
|
||||
return fileList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves information about one file from this archive.
|
||||
/// </summary>
|
||||
/// <param name="path">Path of the file in the archive.</param>
|
||||
/// <returns>File information, or null if the file was not found
|
||||
/// in the archive.</returns>
|
||||
internal ArchiveFileInfo GetFile(string path)
|
||||
{
|
||||
IList<ArchiveFileInfo> files = this.InternalGetFiles(
|
||||
delegate(string match)
|
||||
{
|
||||
return String.Compare(
|
||||
match, path, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
});
|
||||
return (files != null && files.Count > 0 ? files[0] : null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a compression engine that does the low-level work for
|
||||
/// this object.
|
||||
/// </summary>
|
||||
/// <returns>A new compression engine instance that matches the specific
|
||||
/// subclass of archive.</returns>
|
||||
/// <remarks>
|
||||
/// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
|
||||
/// immediately after use.
|
||||
/// </remarks>
|
||||
protected abstract CompressionEngine CreateCompressionEngine();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a case-insensitive dictionary mapping from one list of
|
||||
/// strings to the other.
|
||||
/// </summary>
|
||||
/// <param name="keys">List of keys.</param>
|
||||
/// <param name="values">List of values that are mapped 1-to-1 to
|
||||
/// the keys.</param>
|
||||
/// <returns>A filled dictionary of the strings.</returns>
|
||||
private static IDictionary<string, string> CreateStringDictionary(
|
||||
IList<string> keys, IList<string> values)
|
||||
{
|
||||
IDictionary<string, string> stringDict =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
for (int i = 0; i < keys.Count; i++)
|
||||
{
|
||||
stringDict.Add(keys[i], values[i]);
|
||||
}
|
||||
|
||||
return stringDict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursive-descent helper function for
|
||||
/// GetRelativeFilePathsInDirectoryTree.
|
||||
/// </summary>
|
||||
/// <param name="dir">The root directory of the search.</param>
|
||||
/// <param name="relativeDir">The relative directory to be
|
||||
/// processed now.</param>
|
||||
/// <param name="includeSubdirectories">True to descend into
|
||||
/// subdirectories.</param>
|
||||
/// <param name="fileList">List of files found so far.</param>
|
||||
private void RecursiveGetRelativeFilePathsInDirectoryTree(
|
||||
string dir,
|
||||
string relativeDir,
|
||||
bool includeSubdirectories,
|
||||
IList<string> fileList)
|
||||
{
|
||||
foreach (string file in System.IO.Directory.GetFiles(dir))
|
||||
{
|
||||
string fileName = Path.GetFileName(file);
|
||||
fileList.Add(Path.Combine(relativeDir, fileName));
|
||||
}
|
||||
|
||||
if (includeSubdirectories)
|
||||
{
|
||||
foreach (string subDir in System.IO.Directory.GetDirectories(dir))
|
||||
{
|
||||
string subDirName = Path.GetFileName(subDir);
|
||||
this.RecursiveGetRelativeFilePathsInDirectoryTree(
|
||||
Path.Combine(dir, subDirName),
|
||||
Path.Combine(relativeDir, subDirName),
|
||||
includeSubdirectories,
|
||||
fileList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses a CompressionEngine to get ArchiveFileInfo objects from this
|
||||
/// archive, and then associates them with this ArchiveInfo instance.
|
||||
/// </summary>
|
||||
/// <param name="fileFilter">Optional predicate that can determine
|
||||
/// which files to process.</param>
|
||||
/// <returns>A list of <see cref="ArchiveFileInfo"/> objects, each
|
||||
/// containing information about a file in the archive.</returns>
|
||||
private IList<ArchiveFileInfo> InternalGetFiles(Predicate<string> fileFilter)
|
||||
{
|
||||
using (CompressionEngine compressionEngine = this.CreateCompressionEngine())
|
||||
{
|
||||
ArchiveFileStreamContext streamContext =
|
||||
new ArchiveFileStreamContext(this.FullName, null, null);
|
||||
streamContext.EnableOffsetOpen = true;
|
||||
IList<ArchiveFileInfo> files =
|
||||
compressionEngine.GetFileInfo(streamContext, fileFilter);
|
||||
for (int i = 0; i < files.Count; i++)
|
||||
{
|
||||
files[i].Archive = this;
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveProgressEventArgs.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the data reported in an archive progress event.
|
||||
/// </summary>
|
||||
public class ArchiveProgressEventArgs : EventArgs
|
||||
{
|
||||
private ArchiveProgressType progressType;
|
||||
|
||||
private string currentFileName;
|
||||
private int currentFileNumber;
|
||||
private int totalFiles;
|
||||
private long currentFileBytesProcessed;
|
||||
private long currentFileTotalBytes;
|
||||
|
||||
private string currentArchiveName;
|
||||
private short currentArchiveNumber;
|
||||
private short totalArchives;
|
||||
private long currentArchiveBytesProcessed;
|
||||
private long currentArchiveTotalBytes;
|
||||
|
||||
private long fileBytesProcessed;
|
||||
private long totalFileBytes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ArchiveProgressEventArgs object from specified event parameters.
|
||||
/// </summary>
|
||||
/// <param name="progressType">type of status message</param>
|
||||
/// <param name="currentFileName">name of the file being processed</param>
|
||||
/// <param name="currentFileNumber">number of the current file being processed</param>
|
||||
/// <param name="totalFiles">total number of files to be processed</param>
|
||||
/// <param name="currentFileBytesProcessed">number of bytes processed so far when compressing or extracting a file</param>
|
||||
/// <param name="currentFileTotalBytes">total number of bytes in the current file</param>
|
||||
/// <param name="currentArchiveName">name of the current Archive</param>
|
||||
/// <param name="currentArchiveNumber">current Archive number, when processing a chained set of Archives</param>
|
||||
/// <param name="totalArchives">total number of Archives in a chained set</param>
|
||||
/// <param name="currentArchiveBytesProcessed">number of compressed bytes processed so far during an extraction</param>
|
||||
/// <param name="currentArchiveTotalBytes">total number of compressed bytes to be processed during an extraction</param>
|
||||
/// <param name="fileBytesProcessed">number of uncompressed file bytes processed so far</param>
|
||||
/// <param name="totalFileBytes">total number of uncompressed file bytes to be processed</param>
|
||||
public ArchiveProgressEventArgs(
|
||||
ArchiveProgressType progressType,
|
||||
string currentFileName,
|
||||
int currentFileNumber,
|
||||
int totalFiles,
|
||||
long currentFileBytesProcessed,
|
||||
long currentFileTotalBytes,
|
||||
string currentArchiveName,
|
||||
int currentArchiveNumber,
|
||||
int totalArchives,
|
||||
long currentArchiveBytesProcessed,
|
||||
long currentArchiveTotalBytes,
|
||||
long fileBytesProcessed,
|
||||
long totalFileBytes)
|
||||
{
|
||||
this.progressType = progressType;
|
||||
this.currentFileName = currentFileName;
|
||||
this.currentFileNumber = currentFileNumber;
|
||||
this.totalFiles = totalFiles;
|
||||
this.currentFileBytesProcessed = currentFileBytesProcessed;
|
||||
this.currentFileTotalBytes = currentFileTotalBytes;
|
||||
this.currentArchiveName = currentArchiveName;
|
||||
this.currentArchiveNumber = (short) currentArchiveNumber;
|
||||
this.totalArchives = (short) totalArchives;
|
||||
this.currentArchiveBytesProcessed = currentArchiveBytesProcessed;
|
||||
this.currentArchiveTotalBytes = currentArchiveTotalBytes;
|
||||
this.fileBytesProcessed = fileBytesProcessed;
|
||||
this.totalFileBytes = totalFileBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of status message.
|
||||
/// </summary>
|
||||
/// <value>A <see cref="ArchiveProgressType"/> value indicating what type of progress event occurred.</value>
|
||||
/// <remarks>
|
||||
/// The handler may choose to ignore some types of progress events.
|
||||
/// For example, if the handler will only list each file as it is
|
||||
/// compressed/extracted, it can ignore events that
|
||||
/// are not of type <see cref="ArchiveProgressType.FinishFile"/>.
|
||||
/// </remarks>
|
||||
public ArchiveProgressType ProgressType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.progressType;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file being processed. (The name of the file within the Archive; not the external
|
||||
/// file path.) Also includes the internal path of the file, if any. Valid for
|
||||
/// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
|
||||
/// and <see cref="ArchiveProgressType.FinishFile"/> messages.
|
||||
/// </summary>
|
||||
/// <value>The name of the file currently being processed, or null if processing
|
||||
/// is currently at the stream or archive level.</value>
|
||||
public string CurrentFileName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentFileName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the current file being processed. The first file is number 0, and the last file
|
||||
/// is <see cref="TotalFiles"/>-1. Valid for <see cref="ArchiveProgressType.StartFile"/>,
|
||||
/// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
|
||||
/// </summary>
|
||||
/// <value>The number of the file currently being processed, or the most recent
|
||||
/// file processed if processing is currently at the stream or archive level.</value>
|
||||
public int CurrentFileNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentFileNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of files to be processed. Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The total number of files to be processed that are known so far.</value>
|
||||
public int TotalFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.totalFiles;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes processed so far when compressing or extracting a file. Valid for
|
||||
/// <see cref="ArchiveProgressType.StartFile"/>, <see cref="ArchiveProgressType.PartialFile"/>,
|
||||
/// and <see cref="ArchiveProgressType.FinishFile"/> messages.
|
||||
/// </summary>
|
||||
/// <value>The number of uncompressed bytes processed so far for the current file,
|
||||
/// or 0 if processing is currently at the stream or archive level.</value>
|
||||
public long CurrentFileBytesProcessed
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentFileBytesProcessed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of bytes in the current file. Valid for <see cref="ArchiveProgressType.StartFile"/>,
|
||||
/// <see cref="ArchiveProgressType.PartialFile"/>, and <see cref="ArchiveProgressType.FinishFile"/> messages.
|
||||
/// </summary>
|
||||
/// <value>The uncompressed size of the current file being processed,
|
||||
/// or 0 if processing is currently at the stream or archive level.</value>
|
||||
public long CurrentFileTotalBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentFileTotalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the current archive. Not necessarily the name of the archive on disk.
|
||||
/// Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The name of the current archive, or an empty string if no name was specified.</value>
|
||||
public string CurrentArchiveName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentArchiveName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current archive number, when processing a chained set of archives. Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The number of the current archive.</value>
|
||||
/// <remarks>The first archive is number 0, and the last archive is
|
||||
/// <see cref="TotalArchives"/>-1.</remarks>
|
||||
public int CurrentArchiveNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentArchiveNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of known archives in a chained set. Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The total number of known archives in a chained set.</value>
|
||||
/// <remarks>
|
||||
/// When using the compression option to auto-split into multiple archives based on data size,
|
||||
/// this value will not be accurate until the end.
|
||||
/// </remarks>
|
||||
public int TotalArchives
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.totalArchives;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of compressed bytes processed so far during extraction
|
||||
/// of the current archive. Valid for all extraction messages.
|
||||
/// </summary>
|
||||
/// <value>The number of compressed bytes processed so far during extraction
|
||||
/// of the current archive.</value>
|
||||
public long CurrentArchiveBytesProcessed
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentArchiveBytesProcessed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of compressed bytes to be processed during extraction
|
||||
/// of the current archive. Valid for all extraction messages.
|
||||
/// </summary>
|
||||
/// <value>The total number of compressed bytes to be processed during extraction
|
||||
/// of the current archive.</value>
|
||||
public long CurrentArchiveTotalBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.currentArchiveTotalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of uncompressed bytes processed so far among all files. Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The number of uncompressed file bytes processed so far among all files.</value>
|
||||
/// <remarks>
|
||||
/// When compared to <see cref="TotalFileBytes"/>, this can be used as a measure of overall progress.
|
||||
/// </remarks>
|
||||
public long FileBytesProcessed
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fileBytesProcessed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of uncompressed file bytes to be processed. Valid for all message types.
|
||||
/// </summary>
|
||||
/// <value>The total number of uncompressed bytes to be processed among all files.</value>
|
||||
public long TotalFileBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.totalFileBytes;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string representation of the progress event.
|
||||
/// </summary>
|
||||
/// <returns>a listing of all event parameters and values</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
string formatString =
|
||||
"{0}\n" +
|
||||
"\t CurrentFileName = {1}\n" +
|
||||
"\t CurrentFileNumber = {2}\n" +
|
||||
"\t TotalFiles = {3}\n" +
|
||||
"\t CurrentFileBytesProcessed = {4}\n" +
|
||||
"\t CurrentFileTotalBytes = {5}\n" +
|
||||
"\t CurrentArchiveName = {6}\n" +
|
||||
"\t CurrentArchiveNumber = {7}\n" +
|
||||
"\t TotalArchives = {8}\n" +
|
||||
"\t CurrentArchiveBytesProcessed = {9}\n" +
|
||||
"\t CurrentArchiveTotalBytes = {10}\n" +
|
||||
"\t FileBytesProcessed = {11}\n" +
|
||||
"\t TotalFileBytes = {12}\n";
|
||||
return String.Format(
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
formatString,
|
||||
this.ProgressType,
|
||||
this.CurrentFileName,
|
||||
this.CurrentFileNumber,
|
||||
this.TotalFiles,
|
||||
this.CurrentFileBytesProcessed,
|
||||
this.CurrentFileTotalBytes,
|
||||
this.CurrentArchiveName,
|
||||
this.CurrentArchiveNumber,
|
||||
this.TotalArchives,
|
||||
this.CurrentArchiveBytesProcessed,
|
||||
this.CurrentArchiveTotalBytes,
|
||||
this.FileBytesProcessed,
|
||||
this.TotalFileBytes);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ArchiveProgressType.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of progress event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <p>PACKING EXAMPLE: The following sequence of events might be received when
|
||||
/// extracting a simple archive file with 2 files.</p>
|
||||
/// <list type="table">
|
||||
/// <listheader><term>Message Type</term><description>Description</description></listheader>
|
||||
/// <item><term>StartArchive</term> <description>Begin extracting archive</description></item>
|
||||
/// <item><term>StartFile</term> <description>Begin extracting first file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Extracting first file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Extracting first file</description></item>
|
||||
/// <item><term>FinishFile</term> <description>Finished extracting first file</description></item>
|
||||
/// <item><term>StartFile</term> <description>Begin extracting second file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Extracting second file</description></item>
|
||||
/// <item><term>FinishFile</term> <description>Finished extracting second file</description></item>
|
||||
/// <item><term>FinishArchive</term><description>Finished extracting archive</description></item>
|
||||
/// </list>
|
||||
/// <p></p>
|
||||
/// <p>UNPACKING EXAMPLE: Packing 3 files into 2 archive chunks, where the second file is
|
||||
/// continued to the second archive chunk.</p>
|
||||
/// <list type="table">
|
||||
/// <listheader><term>Message Type</term><description>Description</description></listheader>
|
||||
/// <item><term>StartFile</term> <description>Begin compressing first file</description></item>
|
||||
/// <item><term>FinishFile</term> <description>Finished compressing first file</description></item>
|
||||
/// <item><term>StartFile</term> <description>Begin compressing second file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Compressing second file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Compressing second file</description></item>
|
||||
/// <item><term>FinishFile</term> <description>Finished compressing second file</description></item>
|
||||
/// <item><term>StartArchive</term> <description>Begin writing first archive</description></item>
|
||||
/// <item><term>PartialArchive</term><description>Writing first archive</description></item>
|
||||
/// <item><term>FinishArchive</term> <description>Finished writing first archive</description></item>
|
||||
/// <item><term>StartFile</term> <description>Begin compressing third file</description></item>
|
||||
/// <item><term>PartialFile</term> <description>Compressing third file</description></item>
|
||||
/// <item><term>FinishFile</term> <description>Finished compressing third file</description></item>
|
||||
/// <item><term>StartArchive</term> <description>Begin writing second archive</description></item>
|
||||
/// <item><term>PartialArchive</term><description>Writing second archive</description></item>
|
||||
/// <item><term>FinishArchive</term> <description>Finished writing second archive</description></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public enum ArchiveProgressType : int
|
||||
{
|
||||
/// <summary>Status message before beginning the packing or unpacking an individual file.</summary>
|
||||
StartFile,
|
||||
|
||||
/// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking a file.</summary>
|
||||
PartialFile,
|
||||
|
||||
/// <summary>Status message after completion of the packing or unpacking an individual file.</summary>
|
||||
FinishFile,
|
||||
|
||||
/// <summary>Status message before beginning the packing or unpacking an archive.</summary>
|
||||
StartArchive,
|
||||
|
||||
/// <summary>Status message (possibly reported multiple times) during the process of packing or unpacking an archiv.</summary>
|
||||
PartialArchive,
|
||||
|
||||
/// <summary>Status message after completion of the packing or unpacking of an archive.</summary>
|
||||
FinishArchive,
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="BasicUnpackStreamContext.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Stream context used to extract a single file from an archive into a memory stream.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")]
|
||||
public class BasicUnpackStreamContext : IUnpackStreamContext
|
||||
{
|
||||
private Stream archiveStream;
|
||||
private Stream fileStream;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new BasicExtractStreamContext that reads from the specified archive stream.
|
||||
/// </summary>
|
||||
/// <param name="archiveStream">Archive stream to read from.</param>
|
||||
public BasicUnpackStreamContext(Stream archiveStream)
|
||||
{
|
||||
this.archiveStream = archiveStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stream for the extracted file, or null if no file was extracted.
|
||||
/// </summary>
|
||||
public Stream FileStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fileStream;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the archive stream for reading. Returns a DuplicateStream instance,
|
||||
/// so the stream may be virtually opened multiple times.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The archive number to open (ignored; 0 is assumed).</param>
|
||||
/// <param name="archiveName">The name of the archive being opened.</param>
|
||||
/// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
|
||||
/// <returns>A stream from which archive bytes are read.</returns>
|
||||
public Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine)
|
||||
{
|
||||
return new DuplicateStream(this.archiveStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does *not* close the stream. The archive stream should be managed by
|
||||
/// the code that invokes the archive extraction.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The archive number of the stream to close.</param>
|
||||
/// <param name="archiveName">The name of the archive being closed.</param>
|
||||
/// <param name="stream">The stream being closed.</param>
|
||||
public void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream for writing extracted file bytes. The returned stream is a MemoryStream
|
||||
/// instance, so the file is extracted straight into memory.
|
||||
/// </summary>
|
||||
/// <param name="path">Path of the file within the archive.</param>
|
||||
/// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file.</param>
|
||||
/// <returns>A stream where extracted file bytes are to be written.</returns>
|
||||
public Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime)
|
||||
{
|
||||
this.fileStream = new MemoryStream(new byte[fileSize], 0, (int) fileSize, true, true);
|
||||
return this.fileStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does *not* close the file stream. The file stream is saved in memory so it can
|
||||
/// be read later.
|
||||
/// </summary>
|
||||
/// <param name="path">Path of the file within the archive.</param>
|
||||
/// <param name="stream">The file stream to be closed.</param>
|
||||
/// <param name="attributes">The attributes of the extracted file.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file.</param>
|
||||
public void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabEngine.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Engine capable of packing and unpacking archives in the cabinet format.
|
||||
/// </summary>
|
||||
public class CabEngine : CompressionEngine
|
||||
{
|
||||
private CabPacker packer;
|
||||
private CabUnpacker unpacker;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the cabinet engine.
|
||||
/// </summary>
|
||||
public CabEngine()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the cabinet engine.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If true, the method has been called directly
|
||||
/// or indirectly by a user's code, so managed and unmanaged resources
|
||||
/// will be disposed. If false, the method has been called by the runtime
|
||||
/// from inside the finalizer, and only unmanaged resources will be
|
||||
/// disposed.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (packer != null)
|
||||
{
|
||||
packer.Dispose();
|
||||
packer = null;
|
||||
}
|
||||
if (unpacker != null)
|
||||
{
|
||||
unpacker.Dispose();
|
||||
unpacker = null;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private CabPacker Packer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.packer == null)
|
||||
{
|
||||
this.packer = new CabPacker(this);
|
||||
}
|
||||
|
||||
return this.packer;
|
||||
}
|
||||
}
|
||||
|
||||
private CabUnpacker Unpacker
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.unpacker == null)
|
||||
{
|
||||
this.unpacker = new CabUnpacker(this);
|
||||
}
|
||||
|
||||
return this.unpacker;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cabinet or chain of cabinets.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of cabinet and file streams.</param>
|
||||
/// <param name="files">The paths of the files in the archive (not
|
||||
/// external file paths).</param>
|
||||
/// <param name="maxArchiveSize">The maximum number of bytes for one
|
||||
/// cabinet before the contents are chained to the next cabinet, or zero
|
||||
/// for unlimited cabinet size.</param>
|
||||
/// <exception cref="ArchiveException">The cabinet could not be
|
||||
/// created.</exception>
|
||||
/// <remarks>
|
||||
/// The stream context implementation may provide a mapping from the
|
||||
/// file paths within the cabinet to the external file paths.
|
||||
/// <para>Smaller folder sizes can make it more efficient to extract
|
||||
/// individual files out of large cabinet packages.</para>
|
||||
/// </remarks>
|
||||
public override void Pack(
|
||||
IPackStreamContext streamContext,
|
||||
IEnumerable<string> files,
|
||||
long maxArchiveSize)
|
||||
{
|
||||
this.Packer.CompressionLevel = this.CompressionLevel;
|
||||
this.Packer.UseTempFiles = this.UseTempFiles;
|
||||
this.Packer.Pack(streamContext, files, maxArchiveSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a Stream begins with a header that indicates
|
||||
/// it is a valid cabinet file.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream for reading the cabinet file.</param>
|
||||
/// <returns>True if the stream is a valid cabinet file
|
||||
/// (with no offset); false otherwise.</returns>
|
||||
public override bool IsArchive(Stream stream)
|
||||
{
|
||||
return this.Unpacker.IsArchive(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about files in a cabinet or cabinet chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of cabinet and file streams.</param>
|
||||
/// <param name="fileFilter">A predicate that can determine
|
||||
/// which files to process, optional.</param>
|
||||
/// <returns>Information about files in the cabinet stream.</returns>
|
||||
/// <exception cref="ArchiveException">The cabinet provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public override IList<ArchiveFileInfo> GetFileInfo(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
return this.Unpacker.GetFileInfo(streamContext, fileFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts files from a cabinet or cabinet chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of cabinet and file streams.</param>
|
||||
/// <param name="fileFilter">An optional predicate that can determine
|
||||
/// which files to process.</param>
|
||||
/// <exception cref="ArchiveException">The cabinet provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public override void Unpack(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
this.Unpacker.Unpack(streamContext, fileFilter);
|
||||
}
|
||||
|
||||
internal void ReportProgress(ArchiveProgressEventArgs e)
|
||||
{
|
||||
base.OnProgress(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabException.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
|
||||
/// <summary>
|
||||
/// Exception class for cabinet operations.
|
||||
/// </summary>
|
||||
public class CabException : ArchiveException
|
||||
{
|
||||
private static ResourceManager errorResources;
|
||||
private int error;
|
||||
private int errorCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CabException with a specified error message and a reference to the
|
||||
/// inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception. If the
|
||||
/// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
|
||||
/// is raised in a catch block that handles the inner exception.</param>
|
||||
public CabException(string message, Exception innerException)
|
||||
: this(0, 0, message, innerException) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CabException with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public CabException(string message)
|
||||
: this(0, 0, message, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CabException.
|
||||
/// </summary>
|
||||
public CabException()
|
||||
: this(0, 0, null, null) { }
|
||||
|
||||
internal CabException(int error, int errorCode, string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
this.error = error;
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
internal CabException(int error, int errorCode, string message)
|
||||
: this(error, errorCode, message, null) { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the FCI or FDI cabinet engine error number.
|
||||
/// </summary>
|
||||
/// <value>A cabinet engine error number, or 0 if the exception was
|
||||
/// not related to a cabinet engine error number.</value>
|
||||
public int Error
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.error;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Win32 error code.
|
||||
/// </summary>
|
||||
/// <value>A Win32 error code, or 0 if the exception was
|
||||
/// not related to a Win32 error.</value>
|
||||
public int ErrorCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
internal static ResourceManager ErrorResources
|
||||
{
|
||||
get
|
||||
{
|
||||
if (errorResources == null)
|
||||
{
|
||||
errorResources = new ResourceManager(
|
||||
typeof(CabException).Namespace + ".Errors",
|
||||
#if CORECLR
|
||||
typeof(CabException).GetTypeInfo().Assembly);
|
||||
#else
|
||||
typeof(CabException).Assembly);
|
||||
#endif
|
||||
}
|
||||
return errorResources;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static string GetErrorMessage(int error, int errorCode, bool extracting)
|
||||
{
|
||||
const int FCI_ERROR_RESOURCE_OFFSET = 1000;
|
||||
const int FDI_ERROR_RESOURCE_OFFSET = 2000;
|
||||
int resourceOffset = (extracting ? FDI_ERROR_RESOURCE_OFFSET : FCI_ERROR_RESOURCE_OFFSET);
|
||||
|
||||
string msg = CabException.ErrorResources.GetString(
|
||||
(resourceOffset + error).ToString(CultureInfo.InvariantCulture.NumberFormat),
|
||||
CultureInfo.CurrentUICulture);
|
||||
|
||||
if (msg == null)
|
||||
{
|
||||
msg = CabException.ErrorResources.GetString(
|
||||
resourceOffset.ToString(CultureInfo.InvariantCulture.NumberFormat),
|
||||
CultureInfo.CurrentUICulture);
|
||||
}
|
||||
|
||||
if (errorCode != 0)
|
||||
{
|
||||
const string GENERIC_ERROR_RESOURCE = "1";
|
||||
string msg2 = CabException.ErrorResources.GetString(GENERIC_ERROR_RESOURCE, CultureInfo.CurrentUICulture);
|
||||
msg = String.Format(CultureInfo.InvariantCulture, "{0} " + msg2, msg, errorCode);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabFileInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a compressed file within a cabinet package; provides operations for getting
|
||||
/// the file properties and extracting the file.
|
||||
/// </summary>
|
||||
public class CabFileInfo : ArchiveFileInfo
|
||||
{
|
||||
private int cabFolder;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CabinetFileInfo object representing a file within a cabinet in a specified path.
|
||||
/// </summary>
|
||||
/// <param name="cabinetInfo">An object representing the cabinet containing the file.</param>
|
||||
/// <param name="filePath">The path to the file within the cabinet. Usually, this is a simple file
|
||||
/// name, but if the cabinet contains a directory structure this may include the directory.</param>
|
||||
public CabFileInfo(CabInfo cabinetInfo, string filePath)
|
||||
: base(cabinetInfo, filePath)
|
||||
{
|
||||
if (cabinetInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException("cabinetInfo");
|
||||
}
|
||||
|
||||
this.cabFolder = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CabinetFileInfo object with all parameters specified,
|
||||
/// used internally when reading the metadata out of a cab.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The internal path and name of the file in the cab.</param>
|
||||
/// <param name="cabFolder">The folder number containing the file.</param>
|
||||
/// <param name="cabNumber">The cabinet number where the file starts.</param>
|
||||
/// <param name="attributes">The stored attributes of the file.</param>
|
||||
/// <param name="lastWriteTime">The stored last write time of the file.</param>
|
||||
/// <param name="length">The uncompressed size of the file.</param>
|
||||
internal CabFileInfo(
|
||||
string filePath,
|
||||
int cabFolder,
|
||||
int cabNumber,
|
||||
FileAttributes attributes,
|
||||
DateTime lastWriteTime,
|
||||
long length)
|
||||
: base(filePath, cabNumber, attributes, lastWriteTime, length)
|
||||
{
|
||||
this.cabFolder = cabFolder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cabinet that contains this file.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The CabinetInfo instance that retrieved this file information -- this
|
||||
/// may be null if the CabinetFileInfo object was returned directly from a
|
||||
/// stream.
|
||||
/// </value>
|
||||
public CabInfo Cabinet
|
||||
{
|
||||
get
|
||||
{
|
||||
return (CabInfo)this.Archive;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path of the cabinet that contains this file.
|
||||
/// </summary>
|
||||
/// <value>The full path of the cabinet that contains this file.</value>
|
||||
public string CabinetName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.ArchiveName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of the folder containing this file.
|
||||
/// </summary>
|
||||
/// <value>The number of the cabinet folder containing this file.</value>
|
||||
/// <remarks>A single folder or the first folder of a cabinet
|
||||
/// (or chain of cabinets) is numbered 0.</remarks>
|
||||
public int CabinetFolderNumber
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.cabFolder < 0)
|
||||
{
|
||||
this.Refresh();
|
||||
}
|
||||
return this.cabFolder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the information in this object with new data retrieved
|
||||
/// from an archive.
|
||||
/// </summary>
|
||||
/// <param name="newFileInfo">Fresh instance for the same file just
|
||||
/// read from the archive.</param>
|
||||
/// <remarks>
|
||||
/// This implementation refreshes the <see cref="CabinetFolderNumber"/>.
|
||||
/// </remarks>
|
||||
protected override void Refresh(ArchiveFileInfo newFileInfo)
|
||||
{
|
||||
base.Refresh(newFileInfo);
|
||||
this.cabFolder = ((CabFileInfo)newFileInfo).cabFolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a cabinet file on disk; provides access to
|
||||
/// file-based operations on the cabinet file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally, the methods on this class are much easier to use than the
|
||||
/// stream-based interfaces provided by the <see cref="CabEngine"/> class.
|
||||
/// </remarks>
|
||||
public class CabInfo : ArchiveInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new CabinetInfo object representing a cabinet file in a specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the cabinet file. When creating a cabinet file, this file does not
|
||||
/// necessarily exist yet.</param>
|
||||
public CabInfo(string path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a compression engine that does the low-level work for
|
||||
/// this object.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="CabEngine"/> instance.</returns>
|
||||
/// <remarks>
|
||||
/// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
|
||||
/// immediately after use.
|
||||
/// </remarks>
|
||||
protected override CompressionEngine CreateCompressionEngine()
|
||||
{
|
||||
return new CabEngine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the files contained in the archive.
|
||||
/// </summary>
|
||||
/// <returns>A list of <see cref="CabFileInfo"/> objects, each
|
||||
/// containing information about a file in the archive.</returns>
|
||||
public new IList<CabFileInfo> GetFiles()
|
||||
{
|
||||
IList<ArchiveFileInfo> files = base.GetFiles();
|
||||
List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
|
||||
foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
|
||||
return cabFiles.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the certain files contained in the archive file.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">The search string, such as
|
||||
/// "*.txt".</param>
|
||||
/// <returns>A list of <see cref="CabFileInfo"/> objects, each containing
|
||||
/// information about a file in the archive.</returns>
|
||||
public new IList<CabFileInfo> GetFiles(string searchPattern)
|
||||
{
|
||||
IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
|
||||
List<CabFileInfo> cabFiles = new List<CabFileInfo>(files.Count);
|
||||
foreach (CabFileInfo cabFile in files) cabFiles.Add(cabFile);
|
||||
return cabFiles.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabPacker.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
#if !CORECLR
|
||||
using System.Security.Permissions;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
internal class CabPacker : CabWorker
|
||||
{
|
||||
private const string TempStreamName = "%%TEMP%%";
|
||||
|
||||
private NativeMethods.FCI.Handle fciHandle;
|
||||
|
||||
// These delegates need to be saved as member variables
|
||||
// so that they don't get GC'd.
|
||||
private NativeMethods.FCI.PFNALLOC fciAllocMemHandler;
|
||||
private NativeMethods.FCI.PFNFREE fciFreeMemHandler;
|
||||
private NativeMethods.FCI.PFNOPEN fciOpenStreamHandler;
|
||||
private NativeMethods.FCI.PFNREAD fciReadStreamHandler;
|
||||
private NativeMethods.FCI.PFNWRITE fciWriteStreamHandler;
|
||||
private NativeMethods.FCI.PFNCLOSE fciCloseStreamHandler;
|
||||
private NativeMethods.FCI.PFNSEEK fciSeekStreamHandler;
|
||||
private NativeMethods.FCI.PFNFILEPLACED fciFilePlacedHandler;
|
||||
private NativeMethods.FCI.PFNDELETE fciDeleteFileHandler;
|
||||
private NativeMethods.FCI.PFNGETTEMPFILE fciGetTempFileHandler;
|
||||
|
||||
private NativeMethods.FCI.PFNGETNEXTCABINET fciGetNextCabinet;
|
||||
private NativeMethods.FCI.PFNSTATUS fciCreateStatus;
|
||||
private NativeMethods.FCI.PFNGETOPENINFO fciGetOpenInfo;
|
||||
|
||||
private IPackStreamContext context;
|
||||
|
||||
private FileAttributes fileAttributes;
|
||||
private DateTime fileLastWriteTime;
|
||||
|
||||
private int maxCabBytes;
|
||||
|
||||
private long totalFolderBytesProcessedInCurrentCab;
|
||||
|
||||
private CompressionLevel compressionLevel;
|
||||
private bool dontUseTempFiles;
|
||||
private IList<Stream> tempStreams;
|
||||
|
||||
public CabPacker(CabEngine cabEngine)
|
||||
: base(cabEngine)
|
||||
{
|
||||
this.fciAllocMemHandler = this.CabAllocMem;
|
||||
this.fciFreeMemHandler = this.CabFreeMem;
|
||||
this.fciOpenStreamHandler = this.CabOpenStreamEx;
|
||||
this.fciReadStreamHandler = this.CabReadStreamEx;
|
||||
this.fciWriteStreamHandler = this.CabWriteStreamEx;
|
||||
this.fciCloseStreamHandler = this.CabCloseStreamEx;
|
||||
this.fciSeekStreamHandler = this.CabSeekStreamEx;
|
||||
this.fciFilePlacedHandler = this.CabFilePlaced;
|
||||
this.fciDeleteFileHandler = this.CabDeleteFile;
|
||||
this.fciGetTempFileHandler = this.CabGetTempFile;
|
||||
this.fciGetNextCabinet = this.CabGetNextCabinet;
|
||||
this.fciCreateStatus = this.CabCreateStatus;
|
||||
this.fciGetOpenInfo = this.CabGetOpenInfo;
|
||||
this.tempStreams = new List<Stream>();
|
||||
this.compressionLevel = CompressionLevel.Normal;
|
||||
}
|
||||
|
||||
public bool UseTempFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return !this.dontUseTempFiles;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.dontUseTempFiles = !value;
|
||||
}
|
||||
}
|
||||
|
||||
public CompressionLevel CompressionLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.compressionLevel;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.compressionLevel = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
private void CreateFci(long maxArchiveSize)
|
||||
{
|
||||
NativeMethods.FCI.CCAB ccab = new NativeMethods.FCI.CCAB();
|
||||
if (maxArchiveSize > 0 && maxArchiveSize < ccab.cb)
|
||||
{
|
||||
ccab.cb = Math.Max(
|
||||
NativeMethods.FCI.MIN_DISK, (int)maxArchiveSize);
|
||||
}
|
||||
|
||||
object maxFolderSizeOption = this.context.GetOption(
|
||||
"maxFolderSize", null);
|
||||
if (maxFolderSizeOption != null)
|
||||
{
|
||||
long maxFolderSize = Convert.ToInt64(
|
||||
maxFolderSizeOption, CultureInfo.InvariantCulture);
|
||||
if (maxFolderSize > 0 && maxFolderSize < ccab.cbFolderThresh)
|
||||
{
|
||||
ccab.cbFolderThresh = (int)maxFolderSize;
|
||||
}
|
||||
}
|
||||
|
||||
this.maxCabBytes = ccab.cb;
|
||||
ccab.szCab = this.context.GetArchiveName(0);
|
||||
if (ccab.szCab == null)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
"Cabinet name not provided by stream context.");
|
||||
}
|
||||
ccab.setID = (short)new Random().Next(
|
||||
Int16.MinValue, Int16.MaxValue + 1);
|
||||
this.CabNumbers[ccab.szCab] = 0;
|
||||
this.currentArchiveName = ccab.szCab;
|
||||
this.totalArchives = 1;
|
||||
this.CabStream = null;
|
||||
|
||||
this.Erf.Clear();
|
||||
this.fciHandle = NativeMethods.FCI.Create(
|
||||
this.ErfHandle.AddrOfPinnedObject(),
|
||||
this.fciFilePlacedHandler,
|
||||
this.fciAllocMemHandler,
|
||||
this.fciFreeMemHandler,
|
||||
this.fciOpenStreamHandler,
|
||||
this.fciReadStreamHandler,
|
||||
this.fciWriteStreamHandler,
|
||||
this.fciCloseStreamHandler,
|
||||
this.fciSeekStreamHandler,
|
||||
this.fciDeleteFileHandler,
|
||||
this.fciGetTempFileHandler,
|
||||
ccab,
|
||||
IntPtr.Zero);
|
||||
this.CheckError(false);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")]
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
public void Pack(
|
||||
IPackStreamContext streamContext,
|
||||
IEnumerable<string> files,
|
||||
long maxArchiveSize)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
if (files == null)
|
||||
{
|
||||
throw new ArgumentNullException("files");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.context = streamContext;
|
||||
|
||||
this.ResetProgressData();
|
||||
|
||||
this.CreateFci(maxArchiveSize);
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileAttributes attributes;
|
||||
DateTime lastWriteTime;
|
||||
Stream fileStream = this.context.OpenFileReadStream(
|
||||
file,
|
||||
out attributes,
|
||||
out lastWriteTime);
|
||||
if (fileStream != null)
|
||||
{
|
||||
this.totalFileBytes += fileStream.Length;
|
||||
this.totalFiles++;
|
||||
this.context.CloseFileReadStream(file, fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
long uncompressedBytesInFolder = 0;
|
||||
this.currentFileNumber = -1;
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileAttributes attributes;
|
||||
DateTime lastWriteTime;
|
||||
Stream fileStream = this.context.OpenFileReadStream(
|
||||
file, out attributes, out lastWriteTime);
|
||||
if (fileStream == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileStream.Length >= (long)NativeMethods.FCI.MAX_FOLDER)
|
||||
{
|
||||
throw new NotSupportedException(String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"File {0} exceeds maximum file size " +
|
||||
"for cabinet format.",
|
||||
file));
|
||||
}
|
||||
|
||||
if (uncompressedBytesInFolder > 0)
|
||||
{
|
||||
// Automatically create a new folder if this file
|
||||
// won't fit in the current folder.
|
||||
bool nextFolder = uncompressedBytesInFolder
|
||||
+ fileStream.Length >= (long)NativeMethods.FCI.MAX_FOLDER;
|
||||
|
||||
// Otherwise ask the client if it wants to
|
||||
// move to the next folder.
|
||||
if (!nextFolder)
|
||||
{
|
||||
object nextFolderOption = streamContext.GetOption(
|
||||
"nextFolder",
|
||||
new object[] { file, this.currentFolderNumber });
|
||||
nextFolder = Convert.ToBoolean(
|
||||
nextFolderOption, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (nextFolder)
|
||||
{
|
||||
this.FlushFolder();
|
||||
uncompressedBytesInFolder = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentFolderTotalBytes > 0)
|
||||
{
|
||||
this.currentFolderTotalBytes = 0;
|
||||
this.currentFolderNumber++;
|
||||
uncompressedBytesInFolder = 0;
|
||||
}
|
||||
|
||||
this.currentFileName = file;
|
||||
this.currentFileNumber++;
|
||||
|
||||
this.currentFileTotalBytes = fileStream.Length;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.OnProgress(ArchiveProgressType.StartFile);
|
||||
|
||||
uncompressedBytesInFolder += fileStream.Length;
|
||||
|
||||
this.AddFile(
|
||||
file,
|
||||
fileStream,
|
||||
attributes,
|
||||
lastWriteTime,
|
||||
false,
|
||||
this.CompressionLevel);
|
||||
}
|
||||
|
||||
this.FlushFolder();
|
||||
this.FlushCabinet();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.CabStream != null)
|
||||
{
|
||||
this.context.CloseArchiveWriteStream(
|
||||
this.currentArchiveNumber,
|
||||
this.currentArchiveName,
|
||||
this.CabStream);
|
||||
this.CabStream = null;
|
||||
}
|
||||
|
||||
if (this.FileStream != null)
|
||||
{
|
||||
this.context.CloseFileReadStream(
|
||||
this.currentFileName, this.FileStream);
|
||||
this.FileStream = null;
|
||||
}
|
||||
this.context = null;
|
||||
|
||||
if (this.fciHandle != null)
|
||||
{
|
||||
this.fciHandle.Dispose();
|
||||
this.fciHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
|
||||
{
|
||||
if (this.CabNumbers.ContainsKey(path))
|
||||
{
|
||||
Stream stream = this.CabStream;
|
||||
if (stream == null)
|
||||
{
|
||||
short cabNumber = this.CabNumbers[path];
|
||||
|
||||
this.currentFolderTotalBytes = 0;
|
||||
|
||||
stream = this.context.OpenArchiveWriteStream(cabNumber, path, true, this.CabEngine);
|
||||
if (stream == null)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
|
||||
}
|
||||
this.currentArchiveName = path;
|
||||
|
||||
this.currentArchiveTotalBytes = Math.Min(
|
||||
this.totalFolderBytesProcessedInCurrentCab, this.maxCabBytes);
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
this.CabStream = stream;
|
||||
}
|
||||
path = CabWorker.CabStreamName;
|
||||
}
|
||||
else if (path == CabPacker.TempStreamName)
|
||||
{
|
||||
// Opening memory stream for a temp file.
|
||||
Stream stream = new MemoryStream();
|
||||
this.tempStreams.Add(stream);
|
||||
int streamHandle = this.StreamHandles.AllocHandle(stream);
|
||||
err = 0;
|
||||
return streamHandle;
|
||||
}
|
||||
else if (path != CabWorker.CabStreamName)
|
||||
{
|
||||
// Opening a file on disk for a temp file.
|
||||
path = Path.Combine(Path.GetTempPath(), path);
|
||||
Stream stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
|
||||
this.tempStreams.Add(stream);
|
||||
stream = new DuplicateStream(stream);
|
||||
int streamHandle = this.StreamHandles.AllocHandle(stream);
|
||||
err = 0;
|
||||
return streamHandle;
|
||||
}
|
||||
return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
|
||||
}
|
||||
|
||||
internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
|
||||
{
|
||||
int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
|
||||
if (count > 0 && err == 0)
|
||||
{
|
||||
Stream stream = this.StreamHandles[streamHandle];
|
||||
if (DuplicateStream.OriginalStream(stream) ==
|
||||
DuplicateStream.OriginalStream(this.CabStream))
|
||||
{
|
||||
this.currentArchiveBytesProcessed += cb;
|
||||
if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
|
||||
{
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
|
||||
{
|
||||
Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
|
||||
|
||||
if (stream == DuplicateStream.OriginalStream(this.FileStream))
|
||||
{
|
||||
this.context.CloseFileReadStream(this.currentFileName, stream);
|
||||
this.FileStream = null;
|
||||
long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
|
||||
this.currentFileBytesProcessed += remainder;
|
||||
this.fileBytesProcessed += remainder;
|
||||
this.OnProgress(ArchiveProgressType.FinishFile);
|
||||
|
||||
this.currentFileTotalBytes = 0;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.currentFileName = null;
|
||||
}
|
||||
else if (stream == DuplicateStream.OriginalStream(this.CabStream))
|
||||
{
|
||||
if (stream.CanWrite)
|
||||
{
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
this.currentArchiveNumber++;
|
||||
this.totalArchives++;
|
||||
|
||||
this.context.CloseArchiveWriteStream(
|
||||
this.currentArchiveNumber,
|
||||
this.currentArchiveName,
|
||||
stream);
|
||||
|
||||
this.currentArchiveName = this.NextCabinetName;
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
|
||||
this.totalFolderBytesProcessedInCurrentCab = 0;
|
||||
|
||||
this.CabStream = null;
|
||||
}
|
||||
else // Must be a temp stream
|
||||
{
|
||||
#if CORECLR
|
||||
stream.Dispose();
|
||||
#else
|
||||
stream.Close();
|
||||
#endif
|
||||
this.tempStreams.Remove(stream);
|
||||
}
|
||||
return base.CabCloseStreamEx(streamHandle, out err, pv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the cabinet engine.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
|
||||
/// so managed and unmanaged resources will be disposed. If false, the method has been called by the
|
||||
/// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (this.fciHandle != null)
|
||||
{
|
||||
this.fciHandle.Dispose();
|
||||
this.fciHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeMethods.FCI.TCOMP GetCompressionType(CompressionLevel compLevel)
|
||||
{
|
||||
if (compLevel < CompressionLevel.Min)
|
||||
{
|
||||
return NativeMethods.FCI.TCOMP.TYPE_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (compLevel > CompressionLevel.Max)
|
||||
{
|
||||
compLevel = CompressionLevel.Max;
|
||||
}
|
||||
|
||||
int lzxWindowMax =
|
||||
((int)NativeMethods.FCI.TCOMP.LZX_WINDOW_HI >> (int)NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW) -
|
||||
((int)NativeMethods.FCI.TCOMP.LZX_WINDOW_LO >> (int)NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW);
|
||||
int lzxWindow = lzxWindowMax *
|
||||
(compLevel - CompressionLevel.Min) / (CompressionLevel.Max - CompressionLevel.Min);
|
||||
|
||||
return (NativeMethods.FCI.TCOMP)((int)NativeMethods.FCI.TCOMP.TYPE_LZX |
|
||||
((int)NativeMethods.FCI.TCOMP.LZX_WINDOW_LO +
|
||||
(lzxWindow << (int)NativeMethods.FCI.TCOMP.SHIFT_LZX_WINDOW)));
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
private void AddFile(
|
||||
string name,
|
||||
Stream stream,
|
||||
FileAttributes attributes,
|
||||
DateTime lastWriteTime,
|
||||
bool execute,
|
||||
CompressionLevel compLevel)
|
||||
{
|
||||
this.FileStream = stream;
|
||||
this.fileAttributes = attributes &
|
||||
(FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
|
||||
this.fileLastWriteTime = lastWriteTime;
|
||||
this.currentFileName = name;
|
||||
|
||||
NativeMethods.FCI.TCOMP tcomp = CabPacker.GetCompressionType(compLevel);
|
||||
|
||||
IntPtr namePtr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
Encoding nameEncoding = Encoding.ASCII;
|
||||
if (Encoding.UTF8.GetByteCount(name) > name.Length)
|
||||
{
|
||||
nameEncoding = Encoding.UTF8;
|
||||
this.fileAttributes |= FileAttributes.Normal; // _A_NAME_IS_UTF
|
||||
}
|
||||
|
||||
byte[] nameBytes = nameEncoding.GetBytes(name);
|
||||
namePtr = Marshal.AllocHGlobal(nameBytes.Length + 1);
|
||||
Marshal.Copy(nameBytes, 0, namePtr, nameBytes.Length);
|
||||
Marshal.WriteByte(namePtr, nameBytes.Length, 0);
|
||||
|
||||
this.Erf.Clear();
|
||||
var result = NativeMethods.FCI.AddFile(
|
||||
this.fciHandle,
|
||||
String.Empty,
|
||||
namePtr,
|
||||
execute,
|
||||
this.fciGetNextCabinet,
|
||||
this.fciCreateStatus,
|
||||
this.fciGetOpenInfo,
|
||||
tcomp);
|
||||
if (result == 0)
|
||||
{
|
||||
// Stop compiler from complaining
|
||||
this.CheckError(false);
|
||||
this.FileStream = null;
|
||||
this.currentFileName = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (namePtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(namePtr);
|
||||
}
|
||||
}
|
||||
|
||||
this.CheckError(false);
|
||||
this.FileStream = null;
|
||||
this.currentFileName = null;
|
||||
}
|
||||
|
||||
private void FlushFolder()
|
||||
{
|
||||
this.Erf.Clear();
|
||||
var result = NativeMethods.FCI.FlushFolder(this.fciHandle, this.fciGetNextCabinet, this.fciCreateStatus);
|
||||
if (result == 0)
|
||||
{
|
||||
// Stop compiler from complaining
|
||||
this.CheckError(false);
|
||||
return;
|
||||
}
|
||||
this.CheckError(false);
|
||||
}
|
||||
|
||||
private void FlushCabinet()
|
||||
{
|
||||
this.Erf.Clear();
|
||||
var result = NativeMethods.FCI.FlushCabinet(this.fciHandle, false, this.fciGetNextCabinet, this.fciCreateStatus);
|
||||
if (result == 0)
|
||||
{
|
||||
// Stop compiler from complaining
|
||||
this.CheckError(false);
|
||||
return;
|
||||
}
|
||||
this.CheckError(false);
|
||||
}
|
||||
|
||||
private int CabGetOpenInfo(
|
||||
string path,
|
||||
out short date,
|
||||
out short time,
|
||||
out short attribs,
|
||||
out int err,
|
||||
IntPtr pv)
|
||||
{
|
||||
CompressionEngine.DateTimeToDosDateAndTime(this.fileLastWriteTime, out date, out time);
|
||||
attribs = (short)this.fileAttributes;
|
||||
|
||||
Stream stream = this.FileStream;
|
||||
this.FileStream = new DuplicateStream(stream);
|
||||
int streamHandle = this.StreamHandles.AllocHandle(stream);
|
||||
err = 0;
|
||||
return streamHandle;
|
||||
}
|
||||
|
||||
private int CabFilePlaced(
|
||||
IntPtr pccab,
|
||||
string filePath,
|
||||
long fileSize,
|
||||
int continuation,
|
||||
IntPtr pv)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CabGetNextCabinet(IntPtr pccab, uint prevCabSize, IntPtr pv)
|
||||
{
|
||||
NativeMethods.FCI.CCAB nextCcab = new NativeMethods.FCI.CCAB();
|
||||
Marshal.PtrToStructure(pccab, nextCcab);
|
||||
|
||||
nextCcab.szDisk = String.Empty;
|
||||
nextCcab.szCab = this.context.GetArchiveName(nextCcab.iCab);
|
||||
this.CabNumbers[nextCcab.szCab] = (short)nextCcab.iCab;
|
||||
this.NextCabinetName = nextCcab.szCab;
|
||||
|
||||
Marshal.StructureToPtr(nextCcab, pccab, false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int CabCreateStatus(NativeMethods.FCI.STATUS typeStatus, uint cb1, uint cb2, IntPtr pv)
|
||||
{
|
||||
switch (typeStatus)
|
||||
{
|
||||
case NativeMethods.FCI.STATUS.FILE:
|
||||
if (cb2 > 0 && this.currentFileBytesProcessed < this.currentFileTotalBytes)
|
||||
{
|
||||
if (this.currentFileBytesProcessed + cb2 > this.currentFileTotalBytes)
|
||||
{
|
||||
cb2 = (uint)this.currentFileTotalBytes - (uint)this.currentFileBytesProcessed;
|
||||
}
|
||||
this.currentFileBytesProcessed += cb2;
|
||||
this.fileBytesProcessed += cb2;
|
||||
|
||||
this.OnProgress(ArchiveProgressType.PartialFile);
|
||||
}
|
||||
break;
|
||||
|
||||
case NativeMethods.FCI.STATUS.FOLDER:
|
||||
if (cb1 == 0)
|
||||
{
|
||||
this.currentFolderTotalBytes = cb2 - this.totalFolderBytesProcessedInCurrentCab;
|
||||
this.totalFolderBytesProcessedInCurrentCab = cb2;
|
||||
}
|
||||
else if (this.currentFolderTotalBytes == 0)
|
||||
{
|
||||
this.OnProgress(ArchiveProgressType.PartialArchive);
|
||||
}
|
||||
break;
|
||||
|
||||
case NativeMethods.FCI.STATUS.CABINET:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CabGetTempFile(IntPtr tempNamePtr, int tempNameSize, IntPtr pv)
|
||||
{
|
||||
string tempFileName;
|
||||
if (this.UseTempFiles)
|
||||
{
|
||||
tempFileName = Path.GetFileName(Path.GetTempFileName());
|
||||
}
|
||||
else
|
||||
{
|
||||
tempFileName = CabPacker.TempStreamName;
|
||||
}
|
||||
|
||||
byte[] tempNameBytes = Encoding.ASCII.GetBytes(tempFileName);
|
||||
if (tempNameBytes.Length >= tempNameSize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Marshal.Copy(tempNameBytes, 0, tempNamePtr, tempNameBytes.Length);
|
||||
Marshal.WriteByte(tempNamePtr, tempNameBytes.Length, 0); // null-terminator
|
||||
return 1;
|
||||
}
|
||||
|
||||
private int CabDeleteFile(string path, out int err, IntPtr pv)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Deleting a temp file - don't bother if it is only a memory stream.
|
||||
if (path != CabPacker.TempStreamName)
|
||||
{
|
||||
path = Path.Combine(Path.GetTempPath(), path);
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Failure to delete a temp file is not fatal.
|
||||
}
|
||||
err = 0;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabUnpacker.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
#if !CORECLR
|
||||
using System.Security.Permissions;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
internal class CabUnpacker : CabWorker
|
||||
{
|
||||
private NativeMethods.FDI.Handle fdiHandle;
|
||||
|
||||
// These delegates need to be saved as member variables
|
||||
// so that they don't get GC'd.
|
||||
private NativeMethods.FDI.PFNALLOC fdiAllocMemHandler;
|
||||
private NativeMethods.FDI.PFNFREE fdiFreeMemHandler;
|
||||
private NativeMethods.FDI.PFNOPEN fdiOpenStreamHandler;
|
||||
private NativeMethods.FDI.PFNREAD fdiReadStreamHandler;
|
||||
private NativeMethods.FDI.PFNWRITE fdiWriteStreamHandler;
|
||||
private NativeMethods.FDI.PFNCLOSE fdiCloseStreamHandler;
|
||||
private NativeMethods.FDI.PFNSEEK fdiSeekStreamHandler;
|
||||
|
||||
private IUnpackStreamContext context;
|
||||
|
||||
private List<ArchiveFileInfo> fileList;
|
||||
|
||||
private int folderId;
|
||||
|
||||
private Predicate<string> filter;
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")]
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
public CabUnpacker(CabEngine cabEngine)
|
||||
: base(cabEngine)
|
||||
{
|
||||
this.fdiAllocMemHandler = this.CabAllocMem;
|
||||
this.fdiFreeMemHandler = this.CabFreeMem;
|
||||
this.fdiOpenStreamHandler = this.CabOpenStream;
|
||||
this.fdiReadStreamHandler = this.CabReadStream;
|
||||
this.fdiWriteStreamHandler = this.CabWriteStream;
|
||||
this.fdiCloseStreamHandler = this.CabCloseStream;
|
||||
this.fdiSeekStreamHandler = this.CabSeekStream;
|
||||
|
||||
this.fdiHandle = NativeMethods.FDI.Create(
|
||||
this.fdiAllocMemHandler,
|
||||
this.fdiFreeMemHandler,
|
||||
this.fdiOpenStreamHandler,
|
||||
this.fdiReadStreamHandler,
|
||||
this.fdiWriteStreamHandler,
|
||||
this.fdiCloseStreamHandler,
|
||||
this.fdiSeekStreamHandler,
|
||||
NativeMethods.FDI.CPU_80386,
|
||||
this.ErfHandle.AddrOfPinnedObject());
|
||||
if (this.Erf.Error)
|
||||
{
|
||||
int error = this.Erf.Oper;
|
||||
int errorCode = this.Erf.Type;
|
||||
this.ErfHandle.Free();
|
||||
throw new CabException(
|
||||
error,
|
||||
errorCode,
|
||||
CabException.GetErrorMessage(error, errorCode, true));
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")]
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
public bool IsArchive(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
short id;
|
||||
int folderCount, fileCount;
|
||||
return this.IsCabinet(stream, out id, out folderCount, out fileCount);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")]
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
public IList<ArchiveFileInfo> GetFileInfo(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
this.context = streamContext;
|
||||
this.filter = fileFilter;
|
||||
this.NextCabinetName = String.Empty;
|
||||
this.fileList = new List<ArchiveFileInfo>();
|
||||
bool tmpSuppress = this.SuppressProgressEvents;
|
||||
this.SuppressProgressEvents = true;
|
||||
try
|
||||
{
|
||||
for (short cabNumber = 0;
|
||||
this.NextCabinetName != null;
|
||||
cabNumber++)
|
||||
{
|
||||
this.Erf.Clear();
|
||||
this.CabNumbers[this.NextCabinetName] = cabNumber;
|
||||
|
||||
var result = NativeMethods.FDI.Copy(
|
||||
this.fdiHandle,
|
||||
this.NextCabinetName,
|
||||
String.Empty,
|
||||
0,
|
||||
this.CabListNotify,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero);
|
||||
if (result == 0) {
|
||||
// stop compiler from complaining
|
||||
this.CheckError(true);
|
||||
}
|
||||
this.CheckError(true);
|
||||
}
|
||||
|
||||
List<ArchiveFileInfo> tmpFileList = this.fileList;
|
||||
this.fileList = null;
|
||||
return tmpFileList.AsReadOnly();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.SuppressProgressEvents = tmpSuppress;
|
||||
|
||||
if (this.CabStream != null)
|
||||
{
|
||||
this.context.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber,
|
||||
this.currentArchiveName,
|
||||
this.CabStream);
|
||||
this.CabStream = null;
|
||||
}
|
||||
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts")]
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
public void Unpack(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
IList<ArchiveFileInfo> files =
|
||||
this.GetFileInfo(streamContext, fileFilter);
|
||||
|
||||
this.ResetProgressData();
|
||||
|
||||
if (files != null)
|
||||
{
|
||||
this.totalFiles = files.Count;
|
||||
|
||||
for (int i = 0; i < files.Count; i++)
|
||||
{
|
||||
this.totalFileBytes += files[i].Length;
|
||||
if (files[i].ArchiveNumber >= this.totalArchives)
|
||||
{
|
||||
int totalArchives = files[i].ArchiveNumber + 1;
|
||||
this.totalArchives = (short) totalArchives;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.context = streamContext;
|
||||
this.fileList = null;
|
||||
this.NextCabinetName = String.Empty;
|
||||
this.folderId = -1;
|
||||
this.currentFileNumber = -1;
|
||||
|
||||
try
|
||||
{
|
||||
for (short cabNumber = 0;
|
||||
this.NextCabinetName != null;
|
||||
cabNumber++)
|
||||
{
|
||||
this.Erf.Clear();
|
||||
this.CabNumbers[this.NextCabinetName] = cabNumber;
|
||||
|
||||
var result =NativeMethods.FDI.Copy(
|
||||
this.fdiHandle,
|
||||
this.NextCabinetName,
|
||||
String.Empty,
|
||||
0,
|
||||
this.CabExtractNotify,
|
||||
IntPtr.Zero,
|
||||
IntPtr.Zero);
|
||||
if (result == 0) {
|
||||
// stop compiler from complaining
|
||||
this.CheckError(true);
|
||||
}
|
||||
this.CheckError(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.CabStream != null)
|
||||
{
|
||||
this.context.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber,
|
||||
this.currentArchiveName,
|
||||
this.CabStream);
|
||||
this.CabStream = null;
|
||||
}
|
||||
|
||||
if (this.FileStream != null)
|
||||
{
|
||||
this.context.CloseFileWriteStream(this.currentFileName, this.FileStream, FileAttributes.Normal, DateTime.Now);
|
||||
this.FileStream = null;
|
||||
}
|
||||
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal override int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
|
||||
{
|
||||
if (this.CabNumbers.ContainsKey(path))
|
||||
{
|
||||
Stream stream = this.CabStream;
|
||||
if (stream == null)
|
||||
{
|
||||
short cabNumber = this.CabNumbers[path];
|
||||
|
||||
stream = this.context.OpenArchiveReadStream(cabNumber, path, this.CabEngine);
|
||||
if (stream == null)
|
||||
{
|
||||
throw new FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Cabinet {0} not provided.", cabNumber));
|
||||
}
|
||||
this.currentArchiveName = path;
|
||||
this.currentArchiveNumber = cabNumber;
|
||||
if (this.totalArchives <= this.currentArchiveNumber)
|
||||
{
|
||||
int totalArchives = this.currentArchiveNumber + 1;
|
||||
this.totalArchives = (short) totalArchives;
|
||||
}
|
||||
this.currentArchiveTotalBytes = stream.Length;
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
|
||||
if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
|
||||
{
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
}
|
||||
this.CabStream = stream;
|
||||
}
|
||||
path = CabWorker.CabStreamName;
|
||||
}
|
||||
return base.CabOpenStreamEx(path, openFlags, shareMode, out err, pv);
|
||||
}
|
||||
|
||||
internal override int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
|
||||
{
|
||||
int count = base.CabReadStreamEx(streamHandle, memory, cb, out err, pv);
|
||||
if (err == 0 && this.CabStream != null)
|
||||
{
|
||||
if (this.fileList == null)
|
||||
{
|
||||
Stream stream = this.StreamHandles[streamHandle];
|
||||
if (DuplicateStream.OriginalStream(stream) ==
|
||||
DuplicateStream.OriginalStream(this.CabStream))
|
||||
{
|
||||
this.currentArchiveBytesProcessed += cb;
|
||||
if (this.currentArchiveBytesProcessed > this.currentArchiveTotalBytes)
|
||||
{
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
internal override int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
|
||||
{
|
||||
int count = base.CabWriteStreamEx(streamHandle, memory, cb, out err, pv);
|
||||
if (count > 0 && err == 0)
|
||||
{
|
||||
this.currentFileBytesProcessed += cb;
|
||||
this.fileBytesProcessed += cb;
|
||||
this.OnProgress(ArchiveProgressType.PartialFile);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
internal override int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
|
||||
{
|
||||
Stream stream = DuplicateStream.OriginalStream(this.StreamHandles[streamHandle]);
|
||||
|
||||
if (stream == DuplicateStream.OriginalStream(this.CabStream))
|
||||
{
|
||||
if (this.folderId != -3) // -3 is a special folderId that requires re-opening the same cab
|
||||
{
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
}
|
||||
|
||||
this.context.CloseArchiveReadStream(this.currentArchiveNumber, this.currentArchiveName, stream);
|
||||
|
||||
this.currentArchiveName = this.NextCabinetName;
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes = 0;
|
||||
|
||||
this.CabStream = null;
|
||||
}
|
||||
return base.CabCloseStreamEx(streamHandle, out err, pv);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the cabinet engine.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
|
||||
/// so managed and unmanaged resources will be disposed. If false, the method has been called by the
|
||||
/// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (this.fdiHandle != null)
|
||||
{
|
||||
this.fdiHandle.Dispose();
|
||||
this.fdiHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFileName(NativeMethods.FDI.NOTIFICATION notification)
|
||||
{
|
||||
bool utf8Name = (notification.attribs & (ushort) FileAttributes.Normal) != 0; // _A_NAME_IS_UTF
|
||||
|
||||
// Non-utf8 names should be completely ASCII. But for compatibility with
|
||||
// legacy tools, interpret them using the current (Default) ANSI codepage.
|
||||
Encoding nameEncoding = utf8Name ? Encoding.UTF8 : Encoding.GetEncoding(0);
|
||||
|
||||
// Find how many bytes are in the string.
|
||||
// Unfortunately there is no faster way.
|
||||
int nameBytesCount = 0;
|
||||
while (Marshal.ReadByte(notification.psz1, nameBytesCount) != 0)
|
||||
{
|
||||
nameBytesCount++;
|
||||
}
|
||||
|
||||
byte[] nameBytes = new byte[nameBytesCount];
|
||||
Marshal.Copy(notification.psz1, nameBytes, 0, nameBytesCount);
|
||||
string name = nameEncoding.GetString(nameBytes);
|
||||
if (Path.IsPathRooted(name))
|
||||
{
|
||||
name = name.Replace("" + Path.VolumeSeparatorChar, "");
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private bool IsCabinet(Stream cabStream, out short id, out int cabFolderCount, out int fileCount)
|
||||
{
|
||||
int streamHandle = this.StreamHandles.AllocHandle(cabStream);
|
||||
try
|
||||
{
|
||||
this.Erf.Clear();
|
||||
NativeMethods.FDI.CABINFO fdici;
|
||||
bool isCabinet = 0 != NativeMethods.FDI.IsCabinet(this.fdiHandle, streamHandle, out fdici);
|
||||
|
||||
if (this.Erf.Error)
|
||||
{
|
||||
if (((NativeMethods.FDI.ERROR) this.Erf.Oper) == NativeMethods.FDI.ERROR.UNKNOWN_CABINET_VERSION)
|
||||
{
|
||||
isCabinet = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CabException(
|
||||
this.Erf.Oper,
|
||||
this.Erf.Type,
|
||||
CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, true));
|
||||
}
|
||||
}
|
||||
|
||||
id = fdici.setID;
|
||||
cabFolderCount = (int) fdici.cFolders;
|
||||
fileCount = (int) fdici.cFiles;
|
||||
return isCabinet;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.StreamHandles.FreeHandle(streamHandle);
|
||||
}
|
||||
}
|
||||
|
||||
private int CabListNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
|
||||
{
|
||||
switch (notificationType)
|
||||
{
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
|
||||
{
|
||||
string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
|
||||
this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
|
||||
return 0; // Continue
|
||||
}
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.PARTIAL_FILE:
|
||||
{
|
||||
// This notification can occur when examining the contents of a non-first cab file.
|
||||
return 0; // Continue
|
||||
}
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
|
||||
{
|
||||
//bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
|
||||
|
||||
string name = CabUnpacker.GetFileName(notification);
|
||||
|
||||
if (this.filter == null || this.filter(name))
|
||||
{
|
||||
if (this.fileList != null)
|
||||
{
|
||||
FileAttributes attributes = (FileAttributes) notification.attribs &
|
||||
(FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
|
||||
if (attributes == (FileAttributes) 0)
|
||||
{
|
||||
attributes = FileAttributes.Normal;
|
||||
}
|
||||
DateTime lastWriteTime;
|
||||
CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
|
||||
long length = notification.cb;
|
||||
|
||||
CabFileInfo fileInfo = new CabFileInfo(
|
||||
name,
|
||||
notification.iFolder,
|
||||
notification.iCabinet,
|
||||
attributes,
|
||||
lastWriteTime,
|
||||
length);
|
||||
this.fileList.Add(fileInfo);
|
||||
this.currentFileNumber = this.fileList.Count - 1;
|
||||
this.fileBytesProcessed += notification.cb;
|
||||
}
|
||||
}
|
||||
|
||||
this.totalFiles++;
|
||||
this.totalFileBytes += notification.cb;
|
||||
return 0; // Continue
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CabExtractNotify(NativeMethods.FDI.NOTIFICATIONTYPE notificationType, NativeMethods.FDI.NOTIFICATION notification)
|
||||
{
|
||||
switch (notificationType)
|
||||
{
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.CABINET_INFO:
|
||||
{
|
||||
if (this.NextCabinetName != null && this.NextCabinetName.StartsWith("?", StringComparison.Ordinal))
|
||||
{
|
||||
// We are just continuing the copy of a file that spanned cabinets.
|
||||
// The next cabinet name needs to be preserved.
|
||||
this.NextCabinetName = this.NextCabinetName.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
|
||||
this.NextCabinetName = (nextCab.Length != 0 ? nextCab : null);
|
||||
}
|
||||
return 0; // Continue
|
||||
}
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.NEXT_CABINET:
|
||||
{
|
||||
string nextCab = Marshal.PtrToStringAnsi(notification.psz1);
|
||||
this.CabNumbers[nextCab] = (short) notification.iCabinet;
|
||||
this.NextCabinetName = "?" + this.NextCabinetName;
|
||||
return 0; // Continue
|
||||
}
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.COPY_FILE:
|
||||
{
|
||||
return this.CabExtractCopyFile(notification);
|
||||
}
|
||||
case NativeMethods.FDI.NOTIFICATIONTYPE.CLOSE_FILE_INFO:
|
||||
{
|
||||
return this.CabExtractCloseFile(notification);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int CabExtractCopyFile(NativeMethods.FDI.NOTIFICATION notification)
|
||||
{
|
||||
if (notification.iFolder != this.folderId)
|
||||
{
|
||||
if (notification.iFolder != -3) // -3 is a special folderId used when continuing a folder from a previous cab
|
||||
{
|
||||
if (this.folderId != -1) // -1 means we just started the extraction sequence
|
||||
{
|
||||
this.currentFolderNumber++;
|
||||
}
|
||||
}
|
||||
this.folderId = notification.iFolder;
|
||||
}
|
||||
|
||||
//bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
|
||||
|
||||
string name = CabUnpacker.GetFileName(notification);
|
||||
|
||||
if (this.filter == null || this.filter(name))
|
||||
{
|
||||
this.currentFileNumber++;
|
||||
this.currentFileName = name;
|
||||
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.currentFileTotalBytes = notification.cb;
|
||||
this.OnProgress(ArchiveProgressType.StartFile);
|
||||
|
||||
DateTime lastWriteTime;
|
||||
CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
|
||||
|
||||
Stream stream = this.context.OpenFileWriteStream(name, notification.cb, lastWriteTime);
|
||||
if (stream != null)
|
||||
{
|
||||
this.FileStream = stream;
|
||||
int streamHandle = this.StreamHandles.AllocHandle(stream);
|
||||
return streamHandle;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fileBytesProcessed += notification.cb;
|
||||
this.OnProgress(ArchiveProgressType.FinishFile);
|
||||
this.currentFileName = null;
|
||||
}
|
||||
}
|
||||
return 0; // Continue
|
||||
}
|
||||
|
||||
private int CabExtractCloseFile(NativeMethods.FDI.NOTIFICATION notification)
|
||||
{
|
||||
Stream stream = this.StreamHandles[notification.hf];
|
||||
this.StreamHandles.FreeHandle(notification.hf);
|
||||
|
||||
//bool execute = (notification.attribs & (ushort) FileAttributes.Device) != 0; // _A_EXEC
|
||||
|
||||
string name = CabUnpacker.GetFileName(notification);
|
||||
|
||||
FileAttributes attributes = (FileAttributes) notification.attribs &
|
||||
(FileAttributes.Archive | FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System);
|
||||
if (attributes == (FileAttributes) 0)
|
||||
{
|
||||
attributes = FileAttributes.Normal;
|
||||
}
|
||||
DateTime lastWriteTime;
|
||||
CompressionEngine.DosDateAndTimeToDateTime(notification.date, notification.time, out lastWriteTime);
|
||||
|
||||
stream.Flush();
|
||||
this.context.CloseFileWriteStream(name, stream, attributes, lastWriteTime);
|
||||
this.FileStream = null;
|
||||
|
||||
long remainder = this.currentFileTotalBytes - this.currentFileBytesProcessed;
|
||||
this.currentFileBytesProcessed += remainder;
|
||||
this.fileBytesProcessed += remainder;
|
||||
this.OnProgress(ArchiveProgressType.FinishFile);
|
||||
this.currentFileName = null;
|
||||
|
||||
return 1; // Continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CabWorker.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal abstract class CabWorker : IDisposable
|
||||
{
|
||||
internal const string CabStreamName = "%%CAB%%";
|
||||
|
||||
private CabEngine cabEngine;
|
||||
|
||||
private HandleManager<Stream> streamHandles;
|
||||
private Stream cabStream;
|
||||
private Stream fileStream;
|
||||
|
||||
private NativeMethods.ERF erf;
|
||||
private GCHandle erfHandle;
|
||||
|
||||
private IDictionary<string, short> cabNumbers;
|
||||
private string nextCabinetName;
|
||||
|
||||
private bool suppressProgressEvents;
|
||||
|
||||
private byte[] buf;
|
||||
|
||||
// Progress data
|
||||
protected string currentFileName;
|
||||
protected int currentFileNumber;
|
||||
protected int totalFiles;
|
||||
protected long currentFileBytesProcessed;
|
||||
protected long currentFileTotalBytes;
|
||||
protected short currentFolderNumber;
|
||||
protected long currentFolderTotalBytes;
|
||||
protected string currentArchiveName;
|
||||
protected short currentArchiveNumber;
|
||||
protected short totalArchives;
|
||||
protected long currentArchiveBytesProcessed;
|
||||
protected long currentArchiveTotalBytes;
|
||||
protected long fileBytesProcessed;
|
||||
protected long totalFileBytes;
|
||||
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
protected CabWorker(CabEngine cabEngine)
|
||||
{
|
||||
this.cabEngine = cabEngine;
|
||||
this.streamHandles = new HandleManager<Stream>();
|
||||
this.erf = new NativeMethods.ERF();
|
||||
this.erfHandle = GCHandle.Alloc(this.erf, GCHandleType.Pinned);
|
||||
this.cabNumbers = new Dictionary<string, short>(1);
|
||||
|
||||
// 32K seems to be the size of the largest chunks processed by cabinet.dll.
|
||||
// But just in case, this buffer will auto-enlarge.
|
||||
this.buf = new byte[32768];
|
||||
}
|
||||
|
||||
~CabWorker()
|
||||
{
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
public CabEngine CabEngine
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.cabEngine;
|
||||
}
|
||||
}
|
||||
|
||||
internal NativeMethods.ERF Erf
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.erf;
|
||||
}
|
||||
}
|
||||
|
||||
internal GCHandle ErfHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.erfHandle;
|
||||
}
|
||||
}
|
||||
|
||||
internal HandleManager<Stream> StreamHandles
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.streamHandles;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool SuppressProgressEvents
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.suppressProgressEvents;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.suppressProgressEvents = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal IDictionary<string, short> CabNumbers
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.cabNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
internal string NextCabinetName
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.nextCabinetName;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.nextCabinetName = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal Stream CabStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.cabStream;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.cabStream = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal Stream FileStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fileStream;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.fileStream = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected void ResetProgressData()
|
||||
{
|
||||
this.currentFileName = null;
|
||||
this.currentFileNumber = 0;
|
||||
this.totalFiles = 0;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.currentFileTotalBytes = 0;
|
||||
this.currentFolderNumber = 0;
|
||||
this.currentFolderTotalBytes = 0;
|
||||
this.currentArchiveName = null;
|
||||
this.currentArchiveNumber = 0;
|
||||
this.totalArchives = 0;
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
this.currentArchiveTotalBytes = 0;
|
||||
this.fileBytesProcessed = 0;
|
||||
this.totalFileBytes = 0;
|
||||
}
|
||||
|
||||
protected void OnProgress(ArchiveProgressType progressType)
|
||||
{
|
||||
if (!this.suppressProgressEvents)
|
||||
{
|
||||
ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
|
||||
progressType,
|
||||
this.currentFileName,
|
||||
this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
|
||||
this.totalFiles,
|
||||
this.currentFileBytesProcessed,
|
||||
this.currentFileTotalBytes,
|
||||
this.currentArchiveName,
|
||||
this.currentArchiveNumber,
|
||||
this.totalArchives,
|
||||
this.currentArchiveBytesProcessed,
|
||||
this.currentArchiveTotalBytes,
|
||||
this.fileBytesProcessed,
|
||||
this.totalFileBytes);
|
||||
this.CabEngine.ReportProgress(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr CabAllocMem(int byteCount)
|
||||
{
|
||||
IntPtr memPointer = Marshal.AllocHGlobal((IntPtr) byteCount);
|
||||
return memPointer;
|
||||
}
|
||||
|
||||
internal void CabFreeMem(IntPtr memPointer)
|
||||
{
|
||||
Marshal.FreeHGlobal(memPointer);
|
||||
}
|
||||
|
||||
internal int CabOpenStream(string path, int openFlags, int shareMode)
|
||||
{
|
||||
int err; return this.CabOpenStreamEx(path, openFlags, shareMode, out err, IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal virtual int CabOpenStreamEx(string path, int openFlags, int shareMode, out int err, IntPtr pv)
|
||||
{
|
||||
path = path.Trim();
|
||||
Stream stream = this.cabStream;
|
||||
this.cabStream = new DuplicateStream(stream);
|
||||
int streamHandle = this.streamHandles.AllocHandle(stream);
|
||||
err = 0;
|
||||
return streamHandle;
|
||||
}
|
||||
|
||||
internal int CabReadStream(int streamHandle, IntPtr memory, int cb)
|
||||
{
|
||||
int err; return this.CabReadStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal virtual int CabReadStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
|
||||
{
|
||||
Stream stream = this.streamHandles[streamHandle];
|
||||
int count = (int) cb;
|
||||
if (count > this.buf.Length)
|
||||
{
|
||||
this.buf = new byte[count];
|
||||
}
|
||||
count = stream.Read(this.buf, 0, count);
|
||||
Marshal.Copy(this.buf, 0, memory, count);
|
||||
err = 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
internal int CabWriteStream(int streamHandle, IntPtr memory, int cb)
|
||||
{
|
||||
int err; return this.CabWriteStreamEx(streamHandle, memory, cb, out err, IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal virtual int CabWriteStreamEx(int streamHandle, IntPtr memory, int cb, out int err, IntPtr pv)
|
||||
{
|
||||
Stream stream = this.streamHandles[streamHandle];
|
||||
int count = (int) cb;
|
||||
if (count > this.buf.Length)
|
||||
{
|
||||
this.buf = new byte[count];
|
||||
}
|
||||
Marshal.Copy(memory, this.buf, 0, count);
|
||||
stream.Write(this.buf, 0, count);
|
||||
err = 0;
|
||||
return cb;
|
||||
}
|
||||
|
||||
internal int CabCloseStream(int streamHandle)
|
||||
{
|
||||
int err; return this.CabCloseStreamEx(streamHandle, out err, IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal virtual int CabCloseStreamEx(int streamHandle, out int err, IntPtr pv)
|
||||
{
|
||||
this.streamHandles.FreeHandle(streamHandle);
|
||||
err = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal int CabSeekStream(int streamHandle, int offset, int seekOrigin)
|
||||
{
|
||||
int err; return this.CabSeekStreamEx(streamHandle, offset, seekOrigin, out err, IntPtr.Zero);
|
||||
}
|
||||
|
||||
internal virtual int CabSeekStreamEx(int streamHandle, int offset, int seekOrigin, out int err, IntPtr pv)
|
||||
{
|
||||
Stream stream = this.streamHandles[streamHandle];
|
||||
offset = (int) stream.Seek(offset, (SeekOrigin) seekOrigin);
|
||||
err = 0;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the cabinet engine.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If true, the method has been called directly or indirectly by a user's code,
|
||||
/// so managed and unmanaged resources will be disposed. If false, the method has been called by the
|
||||
/// runtime from inside the finalizer, and only unmanaged resources will be disposed.</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (this.cabStream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
this.cabStream.Dispose();
|
||||
#else
|
||||
this.cabStream.Close();
|
||||
#endif
|
||||
this.cabStream = null;
|
||||
}
|
||||
|
||||
if (this.fileStream != null)
|
||||
{
|
||||
#if CORECLR
|
||||
this.fileStream.Dispose();
|
||||
#else
|
||||
this.fileStream.Close();
|
||||
#endif
|
||||
this.fileStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.erfHandle.IsAllocated)
|
||||
{
|
||||
this.erfHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
protected void CheckError(bool extracting)
|
||||
{
|
||||
if (this.Erf.Error)
|
||||
{
|
||||
throw new CabException(
|
||||
this.Erf.Oper,
|
||||
this.Erf.Type,
|
||||
CabException.GetErrorMessage(this.Erf.Oper, this.Erf.Type, extracting));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="HandleManager.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Generic class for managing allocations of integer handles
|
||||
/// for objects of a certain type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects the handles refer to.</typeparam>
|
||||
internal sealed class HandleManager<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Auto-resizing list of objects for which handles have been allocated.
|
||||
/// Each handle is just an index into this list. When a handle is freed,
|
||||
/// the list item at that index is set to null.
|
||||
/// </summary>
|
||||
private List<T> handles;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new HandleManager instance.
|
||||
/// </summary>
|
||||
public HandleManager()
|
||||
{
|
||||
this.handles = new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object of a handle, or null if the handle is invalid.
|
||||
/// </summary>
|
||||
/// <param name="handle">The integer handle previously allocated
|
||||
/// for the desired object.</param>
|
||||
/// <returns>The object for which the handle was allocated.</returns>
|
||||
public T this[int handle]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (handle > 0 && handle <= this.handles.Count)
|
||||
{
|
||||
return this.handles[handle - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates a new handle for an object.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object that the handle will refer to.</param>
|
||||
/// <returns>New handle that can be later used to retrieve the object.</returns>
|
||||
public int AllocHandle(T obj)
|
||||
{
|
||||
this.handles.Add(obj);
|
||||
int handle = this.handles.Count;
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees a handle that was previously allocated. Afterward the handle
|
||||
/// will be invalid and the object it referred to can no longer retrieved.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle to be freed.</param>
|
||||
public void FreeHandle(int handle)
|
||||
{
|
||||
if (handle > 0 && handle <= this.handles.Count)
|
||||
{
|
||||
this.handles[handle - 1] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="NativeMethods.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Cab
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
#if !CORECLR
|
||||
using System.Security.Permissions;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Native DllImport methods and related structures and constants used for
|
||||
/// cabinet creation and extraction via cabinet.dll.
|
||||
/// </summary>
|
||||
internal static class NativeMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// A direct import of constants, enums, structures, delegates, and functions from fci.h.
|
||||
/// Refer to comments in fci.h for documentation.
|
||||
/// </summary>
|
||||
internal static class FCI
|
||||
{
|
||||
internal const int MIN_DISK = 32768;
|
||||
internal const int MAX_DISK = Int32.MaxValue;
|
||||
internal const int MAX_FOLDER = 0x7FFF8000;
|
||||
internal const int MAX_FILENAME = 256;
|
||||
internal const int MAX_CABINET_NAME = 256;
|
||||
internal const int MAX_CAB_PATH = 256;
|
||||
internal const int MAX_DISK_NAME = 256;
|
||||
|
||||
internal const int CPU_80386 = 1;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int fileHandle, IntPtr memory, int cb, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int fileHandle, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int fileHandle, int dist, int seekType, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNDELETE(string path, out int err, IntPtr pv);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETNEXTCABINET(IntPtr pccab, uint cbPrevCab, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNFILEPLACED(IntPtr pccab, string path, long fileSize, int continuation, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETOPENINFO(string path, out short date, out short time, out short pattribs, out int err, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSTATUS(STATUS typeStatus, uint cb1, uint cb2, IntPtr pv);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNGETTEMPFILE(IntPtr tempNamePtr, int tempNameSize, IntPtr pv);
|
||||
|
||||
/// <summary>
|
||||
/// Error codes that can be returned by FCI.
|
||||
/// </summary>
|
||||
internal enum ERROR : int
|
||||
{
|
||||
NONE,
|
||||
OPEN_SRC,
|
||||
READ_SRC,
|
||||
ALLOC_FAIL,
|
||||
TEMP_FILE,
|
||||
BAD_COMPR_TYPE,
|
||||
CAB_FILE,
|
||||
USER_ABORT,
|
||||
MCI_FAIL,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FCI compression algorithm types and parameters.
|
||||
/// </summary>
|
||||
internal enum TCOMP : ushort
|
||||
{
|
||||
MASK_TYPE = 0x000F,
|
||||
TYPE_NONE = 0x0000,
|
||||
TYPE_MSZIP = 0x0001,
|
||||
TYPE_QUANTUM = 0x0002,
|
||||
TYPE_LZX = 0x0003,
|
||||
BAD = 0x000F,
|
||||
|
||||
MASK_LZX_WINDOW = 0x1F00,
|
||||
LZX_WINDOW_LO = 0x0F00,
|
||||
LZX_WINDOW_HI = 0x1500,
|
||||
SHIFT_LZX_WINDOW = 0x0008,
|
||||
|
||||
MASK_QUANTUM_LEVEL = 0x00F0,
|
||||
QUANTUM_LEVEL_LO = 0x0010,
|
||||
QUANTUM_LEVEL_HI = 0x0070,
|
||||
SHIFT_QUANTUM_LEVEL = 0x0004,
|
||||
|
||||
MASK_QUANTUM_MEM = 0x1F00,
|
||||
QUANTUM_MEM_LO = 0x0A00,
|
||||
QUANTUM_MEM_HI = 0x1500,
|
||||
SHIFT_QUANTUM_MEM = 0x0008,
|
||||
|
||||
MASK_RESERVED = 0xE000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reason for FCI status callback.
|
||||
/// </summary>
|
||||
internal enum STATUS : uint
|
||||
{
|
||||
FILE = 0,
|
||||
FOLDER = 1,
|
||||
CABINET = 2,
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments")]
|
||||
[DllImport("cabinet.dll", EntryPoint = "FCICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Handle Create(IntPtr perf, PFNFILEPLACED pfnfcifp, PFNALLOC pfna, PFNFREE pfnf, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, PFNDELETE pfndelete, PFNGETTEMPFILE pfnfcigtf, [MarshalAs(UnmanagedType.LPStruct)] CCAB pccab, IntPtr pv);
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FCIAddFile", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int AddFile(Handle hfci, string pszSourceFile, IntPtr pszFileName, [MarshalAs(UnmanagedType.Bool)] bool fExecute, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis, PFNGETOPENINFO pfnfcigoi, TCOMP typeCompress);
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FCIFlushCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int FlushCabinet(Handle hfci, [MarshalAs(UnmanagedType.Bool)] bool fGetNextCab, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FCIFlushFolder", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int FlushFolder(Handle hfci, PFNGETNEXTCABINET pfnfcignc, PFNSTATUS pfnfcis);
|
||||
|
||||
#if !CORECLR
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
#endif
|
||||
[DllImport("cabinet.dll", EntryPoint = "FCIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Destroy(IntPtr hfci);
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet information structure used for FCI initialization and GetNextCabinet callback.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
internal class CCAB
|
||||
{
|
||||
internal int cb = MAX_DISK;
|
||||
internal int cbFolderThresh = MAX_FOLDER;
|
||||
internal int cbReserveCFHeader;
|
||||
internal int cbReserveCFFolder;
|
||||
internal int cbReserveCFData;
|
||||
internal int iCab;
|
||||
internal int iDisk;
|
||||
internal int fFailOnIncompressible;
|
||||
internal short setID;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_DISK_NAME)] internal string szDisk = String.Empty;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_CABINET_NAME)] internal string szCab = String.Empty;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_CAB_PATH)] internal string szCabPath = String.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the FCI handle is safely released.
|
||||
/// </summary>
|
||||
internal class Handle : SafeHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new unintialized handle. The handle will be initialized
|
||||
/// when it is marshalled back from native code.
|
||||
/// </summary>
|
||||
internal Handle()
|
||||
: base(IntPtr.Zero, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the handle is invalid. An FCI handle is invalid when it is zero.
|
||||
/// </summary>
|
||||
public override bool IsInvalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.handle == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the handle by calling FDIDestroy().
|
||||
/// </summary>
|
||||
/// <returns>True if the release succeeded.</returns>
|
||||
#if !CORECLR
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
#endif
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return FCI.Destroy(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A direct import of constants, enums, structures, delegates, and functions from fdi.h.
|
||||
/// Refer to comments in fdi.h for documentation.
|
||||
/// </summary>
|
||||
internal static class FDI
|
||||
{
|
||||
internal const int MAX_DISK = Int32.MaxValue;
|
||||
internal const int MAX_FILENAME = 256;
|
||||
internal const int MAX_CABINET_NAME = 256;
|
||||
internal const int MAX_CAB_PATH = 256;
|
||||
internal const int MAX_DISK_NAME = 256;
|
||||
|
||||
internal const int CPU_80386 = 1;
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate IntPtr PFNALLOC(int cb);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void PFNFREE(IntPtr pv);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNOPEN(string path, int oflag, int pmode);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNREAD(int hf, IntPtr pv, int cb);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNWRITE(int hf, IntPtr pv, int cb);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNCLOSE(int hf);
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNSEEK(int hf, int dist, int seektype);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int PFNNOTIFY(NOTIFICATIONTYPE fdint, NOTIFICATION fdin);
|
||||
|
||||
/// <summary>
|
||||
/// Error codes that can be returned by FDI.
|
||||
/// </summary>
|
||||
internal enum ERROR : int
|
||||
{
|
||||
NONE,
|
||||
CABINET_NOT_FOUND,
|
||||
NOT_A_CABINET,
|
||||
UNKNOWN_CABINET_VERSION,
|
||||
CORRUPT_CABINET,
|
||||
ALLOC_FAIL,
|
||||
BAD_COMPR_TYPE,
|
||||
MDI_FAIL,
|
||||
TARGET_FILE,
|
||||
RESERVE_MISMATCH,
|
||||
WRONG_CABINET,
|
||||
USER_ABORT,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of notification message for the FDI Notify callback.
|
||||
/// </summary>
|
||||
internal enum NOTIFICATIONTYPE : int
|
||||
{
|
||||
CABINET_INFO,
|
||||
PARTIAL_FILE,
|
||||
COPY_FILE,
|
||||
CLOSE_FILE_INFO,
|
||||
NEXT_CABINET,
|
||||
ENUMERATE,
|
||||
}
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FDICreate", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern Handle Create([MarshalAs(UnmanagedType.FunctionPtr)] PFNALLOC pfnalloc, [MarshalAs(UnmanagedType.FunctionPtr)] PFNFREE pfnfree, PFNOPEN pfnopen, PFNREAD pfnread, PFNWRITE pfnwrite, PFNCLOSE pfnclose, PFNSEEK pfnseek, int cpuType, IntPtr perf);
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FDICopy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern int Copy(Handle hfdi, string pszCabinet, string pszCabPath, int flags, PFNNOTIFY pfnfdin, IntPtr pfnfdid, IntPtr pvUser);
|
||||
|
||||
#if !CORECLR
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
#endif
|
||||
[DllImport("cabinet.dll", EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool Destroy(IntPtr hfdi);
|
||||
|
||||
[DllImport("cabinet.dll", EntryPoint = "FDIIsCabinet", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", Justification = "FDI file handles definitely remain 4 bytes on 64bit platforms.")]
|
||||
internal static extern int IsCabinet(Handle hfdi, int hf, out CABINFO pfdici);
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet information structure filled in by FDI IsCabinet.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct CABINFO
|
||||
{
|
||||
internal int cbCabinet;
|
||||
internal short cFolders;
|
||||
internal short cFiles;
|
||||
internal short setID;
|
||||
internal short iCabinet;
|
||||
internal int fReserve;
|
||||
internal int hasprev;
|
||||
internal int hasnext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cabinet notification details passed to the FDI Notify callback.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
internal class NOTIFICATION
|
||||
{
|
||||
internal int cb;
|
||||
internal IntPtr psz1;
|
||||
internal IntPtr psz2;
|
||||
internal IntPtr psz3;
|
||||
internal IntPtr pv;
|
||||
|
||||
internal IntPtr hf_ptr;
|
||||
|
||||
internal short date;
|
||||
internal short time;
|
||||
internal short attribs;
|
||||
internal short setID;
|
||||
internal short iCabinet;
|
||||
internal short iFolder;
|
||||
internal int fdie;
|
||||
|
||||
// Unlike all the other file handles in FCI/FDI, this one is
|
||||
// actually pointer-sized. Use a property to pretend it isn't.
|
||||
internal int hf
|
||||
{
|
||||
get { return (int)this.hf_ptr; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the FDI handle is safely released.
|
||||
/// </summary>
|
||||
internal class Handle : SafeHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new unintialized handle. The handle will be initialized
|
||||
/// when it is marshalled back from native code.
|
||||
/// </summary>
|
||||
internal Handle()
|
||||
: base(IntPtr.Zero, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the handle is invalid. An FDI handle is invalid when it is zero.
|
||||
/// </summary>
|
||||
public override bool IsInvalid
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.handle == IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the handle by calling FDIDestroy().
|
||||
/// </summary>
|
||||
/// <returns>True if the release succeeded.</returns>
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return FDI.Destroy(this.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error info structure for FCI and FDI.
|
||||
/// </summary>
|
||||
/// <remarks>Before being passed to FCI or FDI, this structure is
|
||||
/// pinned in memory via a GCHandle. The pinning is necessary
|
||||
/// to be able to read the results, since the ERF structure doesn't
|
||||
/// get marshalled back out after an error.</remarks>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal class ERF
|
||||
{
|
||||
private int erfOper;
|
||||
private int erfType;
|
||||
private int fError;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cabinet error code.
|
||||
/// </summary>
|
||||
internal int Oper
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.erfOper;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.erfOper = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Win32 error code.
|
||||
/// </summary>
|
||||
internal int Type
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.erfType;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.erfType = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GCHandle doesn't like the bool type, so use an int underneath.
|
||||
/// </summary>
|
||||
internal bool Error
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fError != 0;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.fError = value ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the error information.
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
this.Oper = 0;
|
||||
this.Type = 0;
|
||||
this.Error = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CargoStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a source stream and carries additional items that are disposed when the stream is closed.
|
||||
/// </summary>
|
||||
public class CargoStream : Stream
|
||||
{
|
||||
private Stream source;
|
||||
private List<IDisposable> cargo;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new a cargo stream.
|
||||
/// </summary>
|
||||
/// <param name="source">source of the stream</param>
|
||||
/// <param name="cargo">List of additional items that are disposed when the stream is closed.
|
||||
/// The order of the list is the order in which the items are disposed.</param>
|
||||
public CargoStream(Stream source, params IDisposable[] cargo)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.cargo = new List<IDisposable>(cargo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source stream of the cargo stream.
|
||||
/// </summary>
|
||||
public Stream Source
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of additional items that are disposed when the stream is closed.
|
||||
/// The order of the list is the order in which the items are disposed. The contents can be modified any time.
|
||||
/// </summary>
|
||||
public IList<IDisposable> Cargo
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.cargo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports reading.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports reading; otherwise, false.</value>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports writing.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports writing; otherwise, false.</value>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports seeking.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports seeking; otherwise, false.</value>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanSeek;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the source stream.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the source stream.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.Position;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.source.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the source stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
this.source.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the source stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the stream in bytes.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.source.SetLength(value);
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
/// <summary>
|
||||
/// Closes the source stream and also closes the additional objects that are carried.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
this.source.Close();
|
||||
|
||||
foreach (IDisposable cargoObject in this.cargo)
|
||||
{
|
||||
cargoObject.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disposes source stream and additional objects carried
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.source.Dispose();
|
||||
|
||||
foreach (IDisposable cargoObject in this.cargo)
|
||||
{
|
||||
cargoObject.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads from the source stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer
|
||||
/// contains the specified byte array with the values between offset and
|
||||
/// (offset + count - 1) replaced by the bytes read from the source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin
|
||||
/// storing the data read from the stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less
|
||||
/// than the number of bytes requested if that many bytes are not currently available,
|
||||
/// or zero (0) if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return this.source.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the source stream.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count
|
||||
/// bytes from buffer to the stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which
|
||||
/// to begin copying bytes to the stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the stream.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.source.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the position of the source stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type SeekOrigin indicating the reference
|
||||
/// point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return this.source.Seek(offset, origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CompressionEngine.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for an engine capable of packing and unpacking a particular
|
||||
/// compressed file format.
|
||||
/// </summary>
|
||||
public abstract class CompressionEngine : IDisposable
|
||||
{
|
||||
private CompressionLevel compressionLevel;
|
||||
private bool dontUseTempFiles;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the compression engine base class.
|
||||
/// </summary>
|
||||
protected CompressionEngine()
|
||||
{
|
||||
this.compressionLevel = CompressionLevel.Normal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the compression engine.
|
||||
/// </summary>
|
||||
~CompressionEngine()
|
||||
{
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the compression engine reports progress in packing
|
||||
/// or unpacking an archive.
|
||||
/// </summary>
|
||||
/// <seealso cref="ArchiveProgressType"/>
|
||||
public event EventHandler<ArchiveProgressEventArgs> Progress;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag indicating whether temporary files are created
|
||||
/// and used during compression.
|
||||
/// </summary>
|
||||
/// <value>True if temporary files are used; false if compression is done
|
||||
/// entirely in-memory.</value>
|
||||
/// <remarks>The value of this property is true by default. Using temporary
|
||||
/// files can greatly reduce the memory requirement of compression,
|
||||
/// especially when compressing large archives. However, setting this property
|
||||
/// to false may yield slightly better performance when creating small
|
||||
/// archives. Or it may be necessary if the process does not have sufficient
|
||||
/// privileges to create temporary files.</remarks>
|
||||
public bool UseTempFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return !this.dontUseTempFiles;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.dontUseTempFiles = !value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compression level to use when compressing files.
|
||||
/// </summary>
|
||||
/// <value>A compression level ranging from minimum to maximum compression,
|
||||
/// or no compression.</value>
|
||||
public CompressionLevel CompressionLevel
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.compressionLevel;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.compressionLevel = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the compression engine.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an archive.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="files">The paths of the files in the archive
|
||||
/// (not external file paths).</param>
|
||||
/// <exception cref="ArchiveException">The archive could not be
|
||||
/// created.</exception>
|
||||
/// <remarks>
|
||||
/// The stream context implementation may provide a mapping from the
|
||||
/// file paths within the archive to the external file paths.
|
||||
/// </remarks>
|
||||
public void Pack(IPackStreamContext streamContext, IEnumerable<string> files)
|
||||
{
|
||||
if (files == null)
|
||||
{
|
||||
throw new ArgumentNullException("files");
|
||||
}
|
||||
|
||||
this.Pack(streamContext, files, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an archive or chain of archives.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="files">The paths of the files in the archive (not
|
||||
/// external file paths).</param>
|
||||
/// <param name="maxArchiveSize">The maximum number of bytes for one
|
||||
/// archive before the contents are chained to the next archive, or zero
|
||||
/// for unlimited archive size.</param>
|
||||
/// <exception cref="ArchiveException">The archive could not be
|
||||
/// created.</exception>
|
||||
/// <remarks>
|
||||
/// The stream context implementation may provide a mapping from the file
|
||||
/// paths within the archive to the external file paths.
|
||||
/// </remarks>
|
||||
public abstract void Pack(
|
||||
IPackStreamContext streamContext,
|
||||
IEnumerable<string> files,
|
||||
long maxArchiveSize);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a Stream begins with a header that indicates
|
||||
/// it is a valid archive.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream for reading the archive file.</param>
|
||||
/// <returns>True if the stream is a valid archive
|
||||
/// (with no offset); false otherwise.</returns>
|
||||
public abstract bool IsArchive(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of an archive that is positioned 0 or more bytes
|
||||
/// from the start of the Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream for reading the archive.</param>
|
||||
/// <returns>The offset in bytes of the archive,
|
||||
/// or -1 if no archive is found in the Stream.</returns>
|
||||
/// <remarks>The archive must begin on a 4-byte boundary.</remarks>
|
||||
public virtual long FindArchiveOffset(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
long sectionSize = 4;
|
||||
long length = stream.Length;
|
||||
for (long offset = 0; offset <= length - sectionSize; offset += sectionSize)
|
||||
{
|
||||
stream.Seek(offset, SeekOrigin.Begin);
|
||||
if (this.IsArchive(stream))
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about all files in an archive stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream for reading the archive.</param>
|
||||
/// <returns>Information about all files in the archive stream.</returns>
|
||||
/// <exception cref="ArchiveException">The stream is not a valid
|
||||
/// archive.</exception>
|
||||
public IList<ArchiveFileInfo> GetFileInfo(Stream stream)
|
||||
{
|
||||
return this.GetFileInfo(new BasicUnpackStreamContext(stream), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about files in an archive or archive chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="fileFilter">A predicate that can determine
|
||||
/// which files to process, optional.</param>
|
||||
/// <returns>Information about files in the archive stream.</returns>
|
||||
/// <exception cref="ArchiveException">The archive provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public abstract IList<ArchiveFileInfo> GetFileInfo(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of files in an archive Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream for reading the archive.</param>
|
||||
/// <returns>A list of the paths of all files contained in the
|
||||
/// archive.</returns>
|
||||
/// <exception cref="ArchiveException">The stream is not a valid
|
||||
/// archive.</exception>
|
||||
public IList<string> GetFiles(Stream stream)
|
||||
{
|
||||
return this.GetFiles(new BasicUnpackStreamContext(stream), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of files in an archive or archive chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="fileFilter">A predicate that can determine
|
||||
/// which files to process, optional.</param>
|
||||
/// <returns>An array containing the names of all files contained in
|
||||
/// the archive or archive chain.</returns>
|
||||
/// <exception cref="ArchiveException">The archive provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public IList<string> GetFiles(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
IList<ArchiveFileInfo> files =
|
||||
this.GetFileInfo(streamContext, fileFilter);
|
||||
IList<string> fileNames = new List<string>(files.Count);
|
||||
for (int i = 0; i < files.Count; i++)
|
||||
{
|
||||
fileNames.Add(files[i].Name);
|
||||
}
|
||||
|
||||
return fileNames;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a single file from an archive stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream for reading the archive.</param>
|
||||
/// <param name="path">The path of the file within the archive
|
||||
/// (not the external file path).</param>
|
||||
/// <returns>A stream for reading the extracted file, or null
|
||||
/// if the file does not exist in the archive.</returns>
|
||||
/// <exception cref="ArchiveException">The stream is not a valid
|
||||
/// archive.</exception>
|
||||
/// <remarks>The entire extracted file is cached in memory, so this
|
||||
/// method requires enough free memory to hold the file.</remarks>
|
||||
public Stream Unpack(Stream stream, string path)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
BasicUnpackStreamContext streamContext =
|
||||
new BasicUnpackStreamContext(stream);
|
||||
this.Unpack(
|
||||
streamContext,
|
||||
delegate(string match)
|
||||
{
|
||||
return String.Compare(
|
||||
match, path, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
});
|
||||
|
||||
Stream extractStream = streamContext.FileStream;
|
||||
if (extractStream != null)
|
||||
{
|
||||
extractStream.Position = 0;
|
||||
}
|
||||
|
||||
return extractStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts files from an archive or archive chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="fileFilter">An optional predicate that can determine
|
||||
/// which files to process.</param>
|
||||
/// <exception cref="ArchiveException">The archive provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public abstract void Unpack(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter);
|
||||
|
||||
/// <summary>
|
||||
/// Called by sublcasses to distribute a packing or unpacking progress
|
||||
/// event to listeners.
|
||||
/// </summary>
|
||||
/// <param name="e">Event details.</param>
|
||||
protected void OnProgress(ArchiveProgressEventArgs e)
|
||||
{
|
||||
if (this.Progress != null)
|
||||
{
|
||||
this.Progress(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources allocated by the compression engine.
|
||||
/// </summary>
|
||||
/// <param name="disposing">If true, the method has been called
|
||||
/// directly or indirectly by a user's code, so managed and unmanaged
|
||||
/// resources will be disposed. If false, the method has been called by
|
||||
/// the runtime from inside the finalizer, and only unmanaged resources
|
||||
/// will be disposed.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresion utility function for converting old-style
|
||||
/// date and time values to a DateTime structure.
|
||||
/// </summary>
|
||||
public static void DosDateAndTimeToDateTime(
|
||||
short dosDate, short dosTime, out DateTime dateTime)
|
||||
{
|
||||
if (dosDate == 0 && dosTime == 0)
|
||||
{
|
||||
dateTime = DateTime.MinValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
long fileTime;
|
||||
SafeNativeMethods.DosDateTimeToFileTime(dosDate, dosTime, out fileTime);
|
||||
dateTime = DateTime.FromFileTimeUtc(fileTime);
|
||||
dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Local);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compresion utility function for converting a DateTime structure
|
||||
/// to old-style date and time values.
|
||||
/// </summary>
|
||||
public static void DateTimeToDosDateAndTime(
|
||||
DateTime dateTime, out short dosDate, out short dosTime)
|
||||
{
|
||||
dateTime = new DateTime(dateTime.Ticks, DateTimeKind.Utc);
|
||||
long filetime = dateTime.ToFileTimeUtc();
|
||||
SafeNativeMethods.FileTimeToDosDateTime(ref filetime, out dosDate, out dosTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CompressionLevel.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the compression level ranging from minimum compresion to
|
||||
/// maximum compression, or no compression at all.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Although only four values are enumerated, any integral value between
|
||||
/// <see cref="CompressionLevel.Min"/> and <see cref="CompressionLevel.Max"/> can also be used.
|
||||
/// </remarks>
|
||||
public enum CompressionLevel
|
||||
{
|
||||
/// <summary>Do not compress files, only store.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Minimum compression; fastest.</summary>
|
||||
Min = 1,
|
||||
|
||||
/// <summary>A compromize between speed and compression efficiency.</summary>
|
||||
Normal = 6,
|
||||
|
||||
/// <summary>Maximum compression; slowest.</summary>
|
||||
Max = 10
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="DuplicateStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Duplicates a source stream by maintaining a separate position.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// WARNING: duplicate streams are not thread-safe with respect to each other or the original stream.
|
||||
/// If multiple threads use duplicate copies of the same stream, they must synchronize for any operations.
|
||||
/// </remarks>
|
||||
public class DuplicateStream : Stream
|
||||
{
|
||||
private Stream source;
|
||||
private long position;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new duplicate of a stream.
|
||||
/// </summary>
|
||||
/// <param name="source">source of the duplicate</param>
|
||||
public DuplicateStream(Stream source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source");
|
||||
}
|
||||
|
||||
this.source = DuplicateStream.OriginalStream(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original stream that was used to create the duplicate.
|
||||
/// </summary>
|
||||
public Stream Source
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports reading.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports reading; otherwise, false.</value>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports writing.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports writing; otherwise, false.</value>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports seeking.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports seeking; otherwise, false.</value>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanSeek;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the source stream.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the current stream,
|
||||
/// ignoring the position of the source stream.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.position;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.position = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the original stream from a possible duplicate stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">Possible duplicate stream.</param>
|
||||
/// <returns>If the stream is a DuplicateStream, returns
|
||||
/// the duplicate's source; otherwise returns the same stream.</returns>
|
||||
public static Stream OriginalStream(Stream stream)
|
||||
{
|
||||
DuplicateStream dupStream = stream as DuplicateStream;
|
||||
return dupStream != null ? dupStream.Source : stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the source stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
this.source.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the source stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the stream in bytes.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.source.SetLength(value);
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
/// <summary>
|
||||
/// Closes the underlying stream, effectively closing ALL duplicates.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
this.source.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the stream
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.source.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads from the source stream while maintaining a separate position
|
||||
/// and not impacting the source stream's position.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer
|
||||
/// contains the specified byte array with the values between offset and
|
||||
/// (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin
|
||||
/// storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less
|
||||
/// than the number of bytes requested if that many bytes are not currently available,
|
||||
/// or zero (0) if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
long saveSourcePosition = this.source.Position;
|
||||
this.source.Position = this.position;
|
||||
int read = this.source.Read(buffer, offset, count);
|
||||
this.position = this.source.Position;
|
||||
this.source.Position = saveSourcePosition;
|
||||
return read;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to the source stream while maintaining a separate position
|
||||
/// and not impacting the source stream's position.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count
|
||||
/// bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which
|
||||
/// to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the
|
||||
/// current stream.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
long saveSourcePosition = this.source.Position;
|
||||
this.source.Position = this.position;
|
||||
this.source.Write(buffer, offset, count);
|
||||
this.position = this.source.Position;
|
||||
this.source.Position = saveSourcePosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the position of this stream without impacting the
|
||||
/// source stream's position.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type SeekOrigin indicating the reference
|
||||
/// point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the current stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long originPosition = 0;
|
||||
if (origin == SeekOrigin.Current)
|
||||
{
|
||||
originPosition = this.position;
|
||||
}
|
||||
else if (origin == SeekOrigin.End)
|
||||
{
|
||||
originPosition = this.Length;
|
||||
}
|
||||
|
||||
this.position = originPosition + offset;
|
||||
return this.position;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="IPackStreamContext.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// This interface provides the methods necessary for the
|
||||
/// <see cref="CompressionEngine"/> to open and close streams for archives
|
||||
/// and files. The implementor of this interface can use any kind of logic
|
||||
/// to determine what kind of streams to open and where.
|
||||
/// </summary>
|
||||
public interface IPackStreamContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the archive with a specified number.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive
|
||||
/// within the chain.</param>
|
||||
/// <returns>The name of the requested archive. May be an empty string
|
||||
/// for non-chained archives, but may never be null.</returns>
|
||||
/// <remarks>The archive name is the name stored within the archive, used for
|
||||
/// identification of the archive especially among archive chains. That
|
||||
/// name is often, but not necessarily the same as the filename of the
|
||||
/// archive package.</remarks>
|
||||
string GetArchiveName(int archiveNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream for writing an archive package.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive within
|
||||
/// the chain.</param>
|
||||
/// <param name="archiveName">The name of the archive that was returned
|
||||
/// by <see cref="GetArchiveName"/>.</param>
|
||||
/// <param name="truncate">True if the stream should be truncated when
|
||||
/// opened (if it already exists); false if an existing stream is being
|
||||
/// re-opened for writing additional data.</param>
|
||||
/// <param name="compressionEngine">Instance of the compression engine
|
||||
/// doing the operations.</param>
|
||||
/// <returns>A writable Stream where the compressed archive bytes will be
|
||||
/// written, or null to cancel the archive creation.</returns>
|
||||
/// <remarks>
|
||||
/// If this method returns null, the archive engine will throw a
|
||||
/// FileNotFoundException.
|
||||
/// </remarks>
|
||||
Stream OpenArchiveWriteStream(
|
||||
int archiveNumber,
|
||||
string archiveName,
|
||||
bool truncate,
|
||||
CompressionEngine compressionEngine);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an archive package was written.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The 0-based index of the archive within
|
||||
/// the chain.</param>
|
||||
/// <param name="archiveName">The name of the archive that was previously
|
||||
/// returned by
|
||||
/// <see cref="GetArchiveName"/>.</param>
|
||||
/// <param name="stream">A stream that was previously returned by
|
||||
/// <see cref="OpenArchiveWriteStream"/> and is now ready to be closed.</param>
|
||||
/// <remarks>
|
||||
/// If there is another archive package in the chain, then after this stream
|
||||
/// is closed a new stream will be opened.
|
||||
/// </remarks>
|
||||
void CloseArchiveWriteStream(int archiveNumber, string archiveName, Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream to read a file that is to be included in an archive.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive. This is often,
|
||||
/// but not necessarily, the same as the relative path of the file outside
|
||||
/// the archive.</param>
|
||||
/// <param name="attributes">Returned attributes of the opened file, to be
|
||||
/// stored in the archive.</param>
|
||||
/// <param name="lastWriteTime">Returned last-modified time of the opened file,
|
||||
/// to be stored in the archive.</param>
|
||||
/// <returns>A readable Stream where the file bytes will be read from before
|
||||
/// they are compressed, or null to skip inclusion of the file and continue to
|
||||
/// the next file.</returns>
|
||||
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]
|
||||
Stream OpenFileReadStream(
|
||||
string path,
|
||||
out FileAttributes attributes,
|
||||
out DateTime lastWriteTime);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream that has been used to read a file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive; the same as
|
||||
/// the path provided
|
||||
/// when the stream was opened.</param>
|
||||
/// <param name="stream">A stream that was previously returned by
|
||||
/// <see cref="OpenFileReadStream"/> and is now ready to be closed.</param>
|
||||
void CloseFileReadStream(string path, Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets extended parameter information specific to the compression
|
||||
/// format being used.
|
||||
/// </summary>
|
||||
/// <param name="optionName">Name of the option being requested.</param>
|
||||
/// <param name="parameters">Parameters for the option; for per-file options,
|
||||
/// the first parameter is typically the internal file path.</param>
|
||||
/// <returns>Option value, or null to use the default behavior.</returns>
|
||||
/// <remarks>
|
||||
/// This method provides a way to set uncommon options during packaging, or a
|
||||
/// way to handle aspects of compression formats not supported by the base library.
|
||||
/// <para>For example, this may be used by the zip compression library to
|
||||
/// specify different compression methods/levels on a per-file basis.</para>
|
||||
/// <para>The available option names, parameters, and expected return values
|
||||
/// should be documented by each compression library.</para>
|
||||
/// </remarks>
|
||||
object GetOption(string optionName, object[] parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="IUnpackStreamContext.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// This interface provides the methods necessary for the <see cref="CompressionEngine"/> to open
|
||||
/// and close streams for archives and files. The implementor of this interface can use any
|
||||
/// kind of logic to determine what kind of streams to open and where
|
||||
/// </summary>
|
||||
public interface IUnpackStreamContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens the archive stream for reading.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The zero-based index of the archive to open.</param>
|
||||
/// <param name="archiveName">The name of the archive being opened.</param>
|
||||
/// <param name="compressionEngine">Instance of the compression engine doing the operations.</param>
|
||||
/// <returns>A stream from which archive bytes are read, or null to cancel extraction
|
||||
/// of the archive.</returns>
|
||||
/// <remarks>
|
||||
/// When the first archive in a chain is opened, the name is not yet known, so the
|
||||
/// provided value will be an empty string. When opening further archives, the
|
||||
/// provided value is the next-archive name stored in the previous archive. This
|
||||
/// name is often, but not necessarily, the same as the filename of the archive
|
||||
/// package to be opened.
|
||||
/// <para>If this method returns null, the archive engine will throw a
|
||||
/// FileNotFoundException.</para>
|
||||
/// </remarks>
|
||||
Stream OpenArchiveReadStream(int archiveNumber, string archiveName, CompressionEngine compressionEngine);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an archive package was read.
|
||||
/// </summary>
|
||||
/// <param name="archiveNumber">The archive number of the stream to close.</param>
|
||||
/// <param name="archiveName">The name of the archive being closed.</param>
|
||||
/// <param name="stream">The stream that was previously returned by
|
||||
/// <see cref="OpenArchiveReadStream"/> and is now ready to be closed.</param>
|
||||
void CloseArchiveReadStream(int archiveNumber, string archiveName, Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a stream for writing extracted file bytes.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive. This is often, but
|
||||
/// not necessarily, the same as the relative path of the file outside the archive.</param>
|
||||
/// <param name="fileSize">The uncompressed size of the file to be extracted.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file to be extracted.</param>
|
||||
/// <returns>A stream where extracted file bytes are to be written, or null to skip
|
||||
/// extraction of the file and continue to the next file.</returns>
|
||||
/// <remarks>
|
||||
/// The implementor may use the path, size and date information to dynamically
|
||||
/// decide whether or not the file should be extracted.
|
||||
/// </remarks>
|
||||
Stream OpenFileWriteStream(string path, long fileSize, DateTime lastWriteTime);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a stream where an extracted file was written.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file within the archive.</param>
|
||||
/// <param name="stream">The stream that was previously returned by <see cref="OpenFileWriteStream"/>
|
||||
/// and is now ready to be closed.</param>
|
||||
/// <param name="attributes">The attributes of the extracted file.</param>
|
||||
/// <param name="lastWriteTime">The last write time of the file.</param>
|
||||
/// <remarks>
|
||||
/// The implementor may wish to apply the attributes and date to the newly-extracted file.
|
||||
/// </remarks>
|
||||
void CloseFileWriteStream(string path, Stream stream, FileAttributes attributes, DateTime lastWriteTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="OffsetStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a source stream and offsets all read/write/seek calls by a given value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is used to trick archive an packing or unpacking process
|
||||
/// into reading or writing at an offset into a file, primarily for
|
||||
/// self-extracting packages.
|
||||
/// </remarks>
|
||||
public class OffsetStream : Stream
|
||||
{
|
||||
private Stream source;
|
||||
private long sourceOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new OffsetStream instance from a source stream
|
||||
/// and using a specified offset.
|
||||
/// </summary>
|
||||
/// <param name="source">Underlying stream for which all calls will be offset.</param>
|
||||
/// <param name="offset">Positive or negative number of bytes to offset.</param>
|
||||
public OffsetStream(Stream source, long offset)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source");
|
||||
}
|
||||
|
||||
this.source = source;
|
||||
this.sourceOffset = offset;
|
||||
|
||||
this.source.Seek(this.sourceOffset, SeekOrigin.Current);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying stream that this OffsetStream calls into.
|
||||
/// </summary>
|
||||
public Stream Source
|
||||
{
|
||||
get { return this.source; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes to offset all calls before
|
||||
/// redirecting to the underlying stream.
|
||||
/// </summary>
|
||||
public long Offset
|
||||
{
|
||||
get { return this.sourceOffset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports reading.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports reading; otherwise, false.</value>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports writing.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports writing; otherwise, false.</value>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports seeking.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports seeking; otherwise, false.</value>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanSeek;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective length of the stream, which is equal to
|
||||
/// the length of the source stream minus the offset.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { return this.source.Length - this.sourceOffset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the effective position of the stream, which
|
||||
/// is equal to the position of the source stream minus the offset.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get { return this.source.Position - this.sourceOffset; }
|
||||
set { this.source.Position = value + this.sourceOffset; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the source stream and advances
|
||||
/// the position within the stream by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer
|
||||
/// contains the specified byte array with the values between offset and
|
||||
/// (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin
|
||||
/// storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less
|
||||
/// than the number of bytes requested if that many bytes are not currently available,
|
||||
/// or zero (0) if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return this.source.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the source stream and advances the
|
||||
/// current position within this stream by the number of bytes written.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count
|
||||
/// bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which
|
||||
/// to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the
|
||||
/// current stream.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.source.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from the stream and advances the position within the
|
||||
/// source stream by one byte, or returns -1 if at the end of the stream.
|
||||
/// </summary>
|
||||
/// <returns>The unsigned byte cast to an Int32, or -1 if at the
|
||||
/// end of the stream.</returns>
|
||||
public override int ReadByte()
|
||||
{
|
||||
return this.source.ReadByte();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte to the current position in the source stream and
|
||||
/// advances the position within the stream by one byte.
|
||||
/// </summary>
|
||||
/// <param name="value">The byte to write to the stream.</param>
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
this.source.WriteByte(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the source stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
this.source.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the current stream, which is
|
||||
/// equal to the position within the source stream minus the offset.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type SeekOrigin indicating
|
||||
/// the reference point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the current stream.</returns>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return this.source.Seek(offset + (origin == SeekOrigin.Begin ? this.sourceOffset : 0), origin) - this.sourceOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the effective length of the stream, which is equal to
|
||||
/// the length of the source stream minus the offset.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the
|
||||
/// current stream in bytes.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.source.SetLength(value + this.sourceOffset);
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
/// <summary>
|
||||
/// Closes the underlying stream.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
this.source.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the underlying stream
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.source.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="SafeNativeMethods.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
#if !CORECLR
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
#endif
|
||||
internal static class SafeNativeMethods
|
||||
{
|
||||
#if !CORECLR
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
#else
|
||||
[DllImport("api-ms-win-core-kernel32-legacy-l1-1-1.dll", SetLastError=true)]
|
||||
#endif
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool DosDateTimeToFileTime(
|
||||
short wFatDate, short wFatTime, out long fileTime);
|
||||
|
||||
#if !CORECLR
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
#else
|
||||
[DllImport("api-ms-win-core-kernel32-legacy-l1-1-1.dll", SetLastError=true)]
|
||||
#endif
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool FileTimeToDosDateTime(
|
||||
ref long fileTime, out short wFatDate, out short wFatTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ConcatStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Used to trick a DeflateStream into reading from or writing to
|
||||
/// a series of (chunked) streams instead of a single steream.
|
||||
/// </summary>
|
||||
internal class ConcatStream : Stream
|
||||
{
|
||||
private Stream source;
|
||||
private long position;
|
||||
private long length;
|
||||
private Action<ConcatStream> nextStreamHandler;
|
||||
|
||||
public ConcatStream(Action<ConcatStream> nextStreamHandler)
|
||||
{
|
||||
if (nextStreamHandler == null)
|
||||
{
|
||||
throw new ArgumentNullException("nextStreamHandler");
|
||||
}
|
||||
|
||||
this.nextStreamHandler = nextStreamHandler;
|
||||
this.length = Int64.MaxValue;
|
||||
}
|
||||
|
||||
public Stream Source
|
||||
{
|
||||
get { return this.source; }
|
||||
set { this.source = value; }
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.length;
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { return this.position; }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (this.source == null)
|
||||
{
|
||||
this.nextStreamHandler(this);
|
||||
}
|
||||
|
||||
count = (int) Math.Min(count, this.length - this.position);
|
||||
|
||||
int bytesRemaining = count;
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
if (this.source == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
int partialCount = (int) Math.Min(bytesRemaining,
|
||||
this.source.Length - this.source.Position);
|
||||
|
||||
if (partialCount == 0)
|
||||
{
|
||||
this.nextStreamHandler(this);
|
||||
continue;
|
||||
}
|
||||
|
||||
partialCount = this.source.Read(
|
||||
buffer, offset + count - bytesRemaining, partialCount);
|
||||
bytesRemaining -= partialCount;
|
||||
this.position += partialCount;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (this.source == null)
|
||||
{
|
||||
this.nextStreamHandler(this);
|
||||
}
|
||||
|
||||
int bytesRemaining = count;
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
if (this.source == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
int partialCount = (int) Math.Min(bytesRemaining,
|
||||
Math.Max(0, this.length - this.source.Position));
|
||||
|
||||
if (partialCount == 0)
|
||||
{
|
||||
this.nextStreamHandler(this);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.source.Write(
|
||||
buffer, offset + count - bytesRemaining, partialCount);
|
||||
bytesRemaining -= partialCount;
|
||||
this.position += partialCount;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
if (this.source != null)
|
||||
{
|
||||
this.source.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.length = value;
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
/// <summary>
|
||||
/// Closes underying stream
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
if (this.source != null)
|
||||
{
|
||||
this.source.Close();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disposes underlying stream
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.source.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="CrcStream.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a source stream and calcaluates a CRC over all bytes that are read or written.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The CRC algorithm matches that used in the standard ZIP file format.
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
|
||||
public class CrcStream : Stream
|
||||
{
|
||||
private Stream source;
|
||||
private uint crc;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new CrcStream instance from a source stream.
|
||||
/// </summary>
|
||||
/// <param name="source">Underlying stream where bytes will be read from or written to.</param>
|
||||
public CrcStream(Stream source)
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current CRC over all bytes that have been read or written
|
||||
/// since this instance was created.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Crc")]
|
||||
public uint Crc
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.crc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying stream that this stream reads from or writes to.
|
||||
/// </summary>
|
||||
public Stream Source
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports reading.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports reading; otherwise, false.</value>
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports writing.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports writing; otherwise, false.</value>
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the source stream supports seeking.
|
||||
/// </summary>
|
||||
/// <value>true if the stream supports seeking; otherwise, false.</value>
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.CanSeek;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the source stream.
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the position of the source stream.
|
||||
/// </summary>
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.source.Position;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.source.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position within the source stream.
|
||||
/// </summary>
|
||||
/// <param name="offset">A byte offset relative to the origin parameter.</param>
|
||||
/// <param name="origin">A value of type SeekOrigin indicating
|
||||
/// the reference point used to obtain the new position.</param>
|
||||
/// <returns>The new position within the source stream.</returns>
|
||||
/// <remarks>
|
||||
/// Note the CRC is only calculated over bytes that are actually read or
|
||||
/// written, so any bytes skipped by seeking will not contribute to the CRC.
|
||||
/// </remarks>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
return this.source.Seek(offset, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the length of the source stream.
|
||||
/// </summary>
|
||||
/// <param name="value">The desired length of the
|
||||
/// stream in bytes.</param>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.source.SetLength(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a sequence of bytes from the source stream and advances
|
||||
/// the position within the stream by the number of bytes read.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. When this method returns, the buffer
|
||||
/// contains the specified byte array with the values between offset and
|
||||
/// (offset + count - 1) replaced by the bytes read from the current source.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which to begin
|
||||
/// storing the data read from the current stream.</param>
|
||||
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
|
||||
/// <returns>The total number of bytes read into the buffer. This can be less
|
||||
/// than the number of bytes requested if that many bytes are not currently available,
|
||||
/// or zero (0) if the end of the stream has been reached.</returns>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
count = this.source.Read(buffer, offset, count);
|
||||
this.UpdateCrc(buffer, offset, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the source stream and advances the
|
||||
/// current position within this stream by the number of bytes written.
|
||||
/// </summary>
|
||||
/// <param name="buffer">An array of bytes. This method copies count
|
||||
/// bytes from buffer to the current stream.</param>
|
||||
/// <param name="offset">The zero-based byte offset in buffer at which
|
||||
/// to begin copying bytes to the current stream.</param>
|
||||
/// <param name="count">The number of bytes to be written to the
|
||||
/// current stream.</param>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.source.Write(buffer, offset, count);
|
||||
this.UpdateCrc(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes the source stream.
|
||||
/// </summary>
|
||||
public override void Flush()
|
||||
{
|
||||
this.source.Flush();
|
||||
}
|
||||
|
||||
#if !CORECLR
|
||||
/// <summary>
|
||||
/// Closes the underlying stream.
|
||||
/// </summary>
|
||||
public override void Close()
|
||||
{
|
||||
this.source.Close();
|
||||
base.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disposes underlying stream.
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.source.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the CRC with a range of bytes that were read or written.
|
||||
/// </summary>
|
||||
private void UpdateCrc(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.crc = ~this.crc;
|
||||
for( ; count > 0; count--, offset++)
|
||||
{
|
||||
this.crc = (this.crc >> 8) ^
|
||||
CrcStream.crcTable[(this.crc & 0xFF) ^ buffer[offset]];
|
||||
}
|
||||
this.crc = ~this.crc;
|
||||
}
|
||||
|
||||
private static uint[] crcTable = MakeCrcTable();
|
||||
|
||||
/// <summary>
|
||||
/// Computes a table that speeds up calculation of the CRC.
|
||||
/// </summary>
|
||||
private static uint[] MakeCrcTable()
|
||||
{
|
||||
const uint poly = 0x04C11DB7u;
|
||||
uint[] crcTable = new uint[256];
|
||||
for(uint n = 0; n < 256; n++)
|
||||
{
|
||||
uint c = CrcStream.Reflect(n, 8);
|
||||
c = c << 24;
|
||||
for(uint k = 0; k < 8; k++)
|
||||
{
|
||||
c = (c << 1) ^ ((c & 0x80000000u) != 0 ? poly : 0);
|
||||
}
|
||||
crcTable[n] = CrcStream.Reflect(c, 32);
|
||||
}
|
||||
return crcTable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reflects the ordering of certain number of bits. For exmample when reflecting
|
||||
/// one byte, bit one is swapped with bit eight, bit two with bit seven, etc.
|
||||
/// </summary>
|
||||
private static uint Reflect(uint value, int bits)
|
||||
{
|
||||
for (int i = 0; i < bits / 2; i++)
|
||||
{
|
||||
uint leftBit = 1u << (bits - 1 - i);
|
||||
uint rightBit = 1u << i;
|
||||
if (((value & leftBit) != 0) != ((value & rightBit) != 0))
|
||||
{
|
||||
value ^= leftBit | rightBit;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipCompressionMethod.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the compression method or "algorithm"
|
||||
/// used for a single file within a zip archive.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Proprietary zip implementations may define additional compression
|
||||
/// methods outside of those included here.
|
||||
/// </remarks>
|
||||
public enum ZipCompressionMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// The file is stored (no compression)
|
||||
/// </summary>
|
||||
Store = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Shrunk
|
||||
/// </summary>
|
||||
Shrink = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Reduced with compression factor 1
|
||||
/// </summary>
|
||||
Reduce1 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Reduced with compression factor 2
|
||||
/// </summary>
|
||||
Reduce2 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Reduced with compression factor 3
|
||||
/// </summary>
|
||||
Reduce3 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Reduced with compression factor 4
|
||||
/// </summary>
|
||||
Reduce4 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Imploded
|
||||
/// </summary>
|
||||
Implode = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Deflated;
|
||||
/// the most common and widely-compatible form of zip compression.
|
||||
/// </summary>
|
||||
Deflate = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The file is Deflated using the enhanced Deflate64 method.
|
||||
/// </summary>
|
||||
Deflate64 = 9,
|
||||
|
||||
/// <summary>
|
||||
/// The file is compressed using the BZIP2 algorithm.
|
||||
/// </summary>
|
||||
BZip2 = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The file is compressed using the LZMA algorithm.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lzma")]
|
||||
Lzma = 14,
|
||||
|
||||
/// <summary>
|
||||
/// The file is compressed using the PPMd algorithm.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppmd")]
|
||||
Ppmd = 98
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipEngine.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
|
||||
/// <summary>
|
||||
/// Engine capable of packing and unpacking archives in the zip format.
|
||||
/// </summary>
|
||||
public partial class ZipEngine : CompressionEngine
|
||||
{
|
||||
private static Dictionary<ZipCompressionMethod, Func<Stream, Stream>>
|
||||
compressionStreamCreators;
|
||||
private static Dictionary<ZipCompressionMethod, Func<Stream, Stream>>
|
||||
decompressionStreamCreators;
|
||||
|
||||
private static void InitCompressionStreamCreators()
|
||||
{
|
||||
if (ZipEngine.compressionStreamCreators == null)
|
||||
{
|
||||
ZipEngine.compressionStreamCreators = new
|
||||
Dictionary<ZipCompressionMethod, Func<Stream, Stream>>();
|
||||
ZipEngine.decompressionStreamCreators = new
|
||||
Dictionary<ZipCompressionMethod, Func<Stream, Stream>>();
|
||||
|
||||
ZipEngine.RegisterCompressionStreamCreator(
|
||||
ZipCompressionMethod.Store,
|
||||
CompressionMode.Compress,
|
||||
delegate(Stream stream)
|
||||
{
|
||||
return stream;
|
||||
});
|
||||
ZipEngine.RegisterCompressionStreamCreator(
|
||||
ZipCompressionMethod.Deflate,
|
||||
CompressionMode.Compress,
|
||||
delegate(Stream stream)
|
||||
{
|
||||
return new DeflateStream(stream, CompressionMode.Compress, true);
|
||||
});
|
||||
ZipEngine.RegisterCompressionStreamCreator(
|
||||
ZipCompressionMethod.Store,
|
||||
CompressionMode.Decompress,
|
||||
delegate(Stream stream)
|
||||
{
|
||||
return stream;
|
||||
});
|
||||
ZipEngine.RegisterCompressionStreamCreator(
|
||||
ZipCompressionMethod.Deflate,
|
||||
CompressionMode.Decompress,
|
||||
delegate(Stream stream)
|
||||
{
|
||||
return new DeflateStream(stream, CompressionMode.Decompress, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a delegate that can create a warpper stream for
|
||||
/// compressing or uncompressing the data of a source stream.
|
||||
/// </summary>
|
||||
/// <param name="compressionMethod">Compression method being registered.</param>
|
||||
/// <param name="compressionMode">Indicates registration for ether
|
||||
/// compress or decompress mode.</param>
|
||||
/// <param name="creator">Delegate being registered.</param>
|
||||
/// <remarks>
|
||||
/// For compression, the delegate accepts a stream that writes to the archive
|
||||
/// and returns a wrapper stream that compresses bytes as they are written.
|
||||
/// For decompression, the delegate accepts a stream that reads from the archive
|
||||
/// and returns a wrapper stream that decompresses bytes as they are read.
|
||||
/// This wrapper stream model follows the design used by
|
||||
/// System.IO.Compression.DeflateStream, and indeed that class is used
|
||||
/// to implement the Deflate compression method by default.
|
||||
/// <para>To unregister a delegate, call this method again and pass
|
||||
/// null for the delegate parameter.</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// When the ZipEngine class is initialized, the Deflate compression method
|
||||
/// is automatically registered like this:
|
||||
/// <code>
|
||||
/// ZipEngine.RegisterCompressionStreamCreator(
|
||||
/// ZipCompressionMethod.Deflate,
|
||||
/// CompressionMode.Compress,
|
||||
/// delegate(Stream stream) {
|
||||
/// return new DeflateStream(stream, CompressionMode.Compress, true);
|
||||
/// });
|
||||
/// ZipEngine.RegisterCompressionStreamCreator(
|
||||
/// ZipCompressionMethod.Deflate,
|
||||
/// CompressionMode.Decompress,
|
||||
/// delegate(Stream stream) {
|
||||
/// return new DeflateStream(stream, CompressionMode.Decompress, true);
|
||||
/// });
|
||||
/// </code></example>
|
||||
public static void RegisterCompressionStreamCreator(
|
||||
ZipCompressionMethod compressionMethod,
|
||||
CompressionMode compressionMode,
|
||||
Func<Stream, Stream> creator)
|
||||
{
|
||||
ZipEngine.InitCompressionStreamCreators();
|
||||
if (compressionMode == CompressionMode.Compress)
|
||||
{
|
||||
ZipEngine.compressionStreamCreators[compressionMethod] = creator;
|
||||
}
|
||||
else
|
||||
{
|
||||
ZipEngine.decompressionStreamCreators[compressionMethod] = creator;
|
||||
}
|
||||
}
|
||||
|
||||
// Progress data
|
||||
private string currentFileName;
|
||||
private int currentFileNumber;
|
||||
private int totalFiles;
|
||||
private long currentFileBytesProcessed;
|
||||
private long currentFileTotalBytes;
|
||||
private string mainArchiveName;
|
||||
private string currentArchiveName;
|
||||
private short currentArchiveNumber;
|
||||
private short totalArchives;
|
||||
private long currentArchiveBytesProcessed;
|
||||
private long currentArchiveTotalBytes;
|
||||
private long fileBytesProcessed;
|
||||
private long totalFileBytes;
|
||||
private string comment;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the zip engine.
|
||||
/// </summary>
|
||||
public ZipEngine()
|
||||
: base()
|
||||
{
|
||||
ZipEngine.InitCompressionStreamCreators();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the comment from the last-examined archive,
|
||||
/// or sets the comment to be added to any created archives.
|
||||
/// </summary>
|
||||
public string ArchiveComment
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.comment;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.comment = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a Stream begins with a header that indicates
|
||||
/// it is a valid archive file.
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream for reading the archive file.</param>
|
||||
/// <returns>True if the stream is a valid zip archive
|
||||
/// (with no offset); false otherwise.</returns>
|
||||
public override bool IsArchive(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
if (stream.Length - stream.Position < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
uint sig = reader.ReadUInt32();
|
||||
switch (sig)
|
||||
{
|
||||
case ZipFileHeader.LFHSIG:
|
||||
case ZipEndOfCentralDirectory.EOCDSIG:
|
||||
case ZipEndOfCentralDirectory.EOCD64SIG:
|
||||
case ZipFileHeader.SPANSIG:
|
||||
case ZipFileHeader.SPANSIG2:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of an archive that is positioned 0 or more bytes
|
||||
/// from the start of the Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A stream for reading the archive.</param>
|
||||
/// <returns>The offset in bytes of the archive,
|
||||
/// or -1 if no archive is found in the Stream.</returns>
|
||||
/// <remarks>The archive must begin on a 4-byte boundary.</remarks>
|
||||
public override long FindArchiveOffset(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
long offset = base.FindArchiveOffset(stream);
|
||||
if (offset > 0)
|
||||
{
|
||||
// Some self-extract packages include the exe stub in file offset calculations.
|
||||
// Check the first header directory offset to decide whether the entire
|
||||
// archive needs to be offset or not.
|
||||
|
||||
ZipEndOfCentralDirectory eocd = this.GetEOCD(null, stream);
|
||||
if (eocd != null && eocd.totalEntries > 0)
|
||||
{
|
||||
stream.Seek(eocd.dirOffset, SeekOrigin.Begin);
|
||||
|
||||
ZipFileHeader header = new ZipFileHeader();
|
||||
if (header.Read(stream, true) && header.localHeaderOffset < stream.Length)
|
||||
{
|
||||
stream.Seek(header.localHeaderOffset, SeekOrigin.Begin);
|
||||
if (header.Read(stream, false))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about files in a zip archive or archive chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="fileFilter">A predicate that can determine
|
||||
/// which files to process, optional.</param>
|
||||
/// <returns>Information about files in the archive stream.</returns>
|
||||
/// <exception cref="ArchiveException">The archive provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public override IList<ArchiveFileInfo> GetFileInfo(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
IList<ZipFileHeader> headers = this.GetCentralDirectory(streamContext);
|
||||
if (headers == null)
|
||||
{
|
||||
throw new ZipException("Zip central directory not found.");
|
||||
}
|
||||
|
||||
List<ArchiveFileInfo> files = new List<ArchiveFileInfo>(headers.Count);
|
||||
foreach (ZipFileHeader header in headers)
|
||||
{
|
||||
if (!header.IsDirectory &&
|
||||
(fileFilter == null || fileFilter(header.fileName)))
|
||||
{
|
||||
files.Add(header.ToZipFileInfo());
|
||||
}
|
||||
}
|
||||
|
||||
return files.AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all the file headers from the central directory in the main archive.
|
||||
/// </summary>
|
||||
private IList<ZipFileHeader> GetCentralDirectory(IUnpackStreamContext streamContext)
|
||||
{
|
||||
Stream archiveStream = null;
|
||||
this.currentArchiveNumber = 0;
|
||||
try
|
||||
{
|
||||
List<ZipFileHeader> headers = new List<ZipFileHeader>();
|
||||
archiveStream = this.OpenArchive(streamContext, 0);
|
||||
|
||||
ZipEndOfCentralDirectory eocd = this.GetEOCD(streamContext, archiveStream);
|
||||
if (eocd == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (eocd.totalEntries == 0)
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
headers.Capacity = (int)eocd.totalEntries;
|
||||
|
||||
if (eocd.dirOffset > archiveStream.Length - ZipFileHeader.CFH_FIXEDSIZE)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, archiveStream);
|
||||
archiveStream = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
|
||||
uint sig = new BinaryReader(archiveStream).ReadUInt32();
|
||||
if (sig != ZipFileHeader.CFHSIG)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, archiveStream);
|
||||
archiveStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (archiveStream == null)
|
||||
{
|
||||
this.currentArchiveNumber = (short)(eocd.dirStartDiskNumber + 1);
|
||||
archiveStream = streamContext.OpenArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, this);
|
||||
|
||||
if (archiveStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
archiveStream.Seek(eocd.dirOffset, SeekOrigin.Begin);
|
||||
|
||||
while (headers.Count < eocd.totalEntries)
|
||||
{
|
||||
ZipFileHeader header = new ZipFileHeader();
|
||||
if (!header.Read(archiveStream, true))
|
||||
{
|
||||
throw new ZipException(
|
||||
"Missing or invalid central directory file header");
|
||||
}
|
||||
|
||||
headers.Add(header);
|
||||
|
||||
if (headers.Count < eocd.totalEntries &&
|
||||
archiveStream.Position == archiveStream.Length)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, archiveStream);
|
||||
this.currentArchiveNumber++;
|
||||
archiveStream = streamContext.OpenArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, this);
|
||||
if (archiveStream == null)
|
||||
{
|
||||
this.currentArchiveNumber = 0;
|
||||
archiveStream = streamContext.OpenArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (archiveStream != null)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber, String.Empty, archiveStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates and reads the end of central directory record near the
|
||||
/// end of the archive.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "streamContext")]
|
||||
private ZipEndOfCentralDirectory GetEOCD(
|
||||
IUnpackStreamContext streamContext, Stream archiveStream)
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(archiveStream);
|
||||
long offset = archiveStream.Length
|
||||
- ZipEndOfCentralDirectory.EOCD_RECORD_FIXEDSIZE;
|
||||
while (offset >= 0)
|
||||
{
|
||||
archiveStream.Seek(offset, SeekOrigin.Begin);
|
||||
|
||||
uint sig = reader.ReadUInt32();
|
||||
if (sig == ZipEndOfCentralDirectory.EOCDSIG)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset--;
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
|
||||
archiveStream.Seek(offset, SeekOrigin.Begin);
|
||||
if (!eocd.Read(archiveStream))
|
||||
{
|
||||
throw new ZipException("Invalid end of central directory record");
|
||||
}
|
||||
|
||||
if (eocd.dirOffset == (long)UInt32.MaxValue)
|
||||
{
|
||||
string saveComment = eocd.comment;
|
||||
|
||||
archiveStream.Seek(
|
||||
offset - Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE,
|
||||
SeekOrigin.Begin);
|
||||
|
||||
Zip64EndOfCentralDirectoryLocator eocdl =
|
||||
new Zip64EndOfCentralDirectoryLocator();
|
||||
if (!eocdl.Read(archiveStream))
|
||||
{
|
||||
throw new ZipException("Missing or invalid end of " +
|
||||
"central directory record locator");
|
||||
}
|
||||
|
||||
if (eocdl.dirStartDiskNumber == eocdl.totalDisks - 1)
|
||||
{
|
||||
// ZIP64 eocd is entirely in current stream.
|
||||
archiveStream.Seek(eocdl.dirOffset, SeekOrigin.Begin);
|
||||
if (!eocd.Read(archiveStream))
|
||||
{
|
||||
throw new ZipException("Missing or invalid ZIP64 end of " +
|
||||
"central directory record");
|
||||
}
|
||||
}
|
||||
else if (streamContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: handle EOCD64 spanning archives!
|
||||
throw new NotImplementedException("Zip implementation does not " +
|
||||
"handle end of central directory record that spans archives.");
|
||||
}
|
||||
|
||||
eocd.comment = saveComment;
|
||||
}
|
||||
|
||||
return eocd;
|
||||
}
|
||||
|
||||
private void ResetProgressData()
|
||||
{
|
||||
this.currentFileName = null;
|
||||
this.currentFileNumber = 0;
|
||||
this.totalFiles = 0;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.currentFileTotalBytes = 0;
|
||||
this.currentArchiveName = null;
|
||||
this.currentArchiveNumber = 0;
|
||||
this.totalArchives = 0;
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
this.currentArchiveTotalBytes = 0;
|
||||
this.fileBytesProcessed = 0;
|
||||
this.totalFileBytes = 0;
|
||||
}
|
||||
|
||||
private void OnProgress(ArchiveProgressType progressType)
|
||||
{
|
||||
ArchiveProgressEventArgs e = new ArchiveProgressEventArgs(
|
||||
progressType,
|
||||
this.currentFileName,
|
||||
this.currentFileNumber >= 0 ? this.currentFileNumber : 0,
|
||||
this.totalFiles,
|
||||
this.currentFileBytesProcessed,
|
||||
this.currentFileTotalBytes,
|
||||
this.currentArchiveName,
|
||||
this.currentArchiveNumber,
|
||||
this.totalArchives,
|
||||
this.currentArchiveBytesProcessed,
|
||||
this.currentArchiveTotalBytes,
|
||||
this.fileBytesProcessed,
|
||||
this.totalFileBytes);
|
||||
this.OnProgress(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipException.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Exception class for zip operations.
|
||||
/// </summary>
|
||||
public class ZipException : ArchiveException
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new ZipException with a specified error message and a reference to the
|
||||
/// inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception. If the
|
||||
/// innerException parameter is not a null reference (Nothing in Visual Basic), the current exception
|
||||
/// is raised in a catch block that handles the inner exception.</param>
|
||||
public ZipException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ZipException with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public ZipException(string message)
|
||||
: this(message, null) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ZipException.
|
||||
/// </summary>
|
||||
public ZipException()
|
||||
: this(null, null) { }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipFileInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a compressed file within a zip package; provides operations for getting
|
||||
/// the file properties and extracting the file.
|
||||
/// </summary>
|
||||
public class ZipFileInfo : ArchiveFileInfo
|
||||
{
|
||||
private long compressedLength;
|
||||
private ZipCompressionMethod compressionMethod;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ZipFileInfo object representing a file within a zip in a specified path.
|
||||
/// </summary>
|
||||
/// <param name="zipInfo">An object representing the zip archive containing the file.</param>
|
||||
/// <param name="filePath">The path to the file within the zip archive. Usually, this is a simple file
|
||||
/// name, but if the zip archive contains a directory structure this may include the directory.</param>
|
||||
public ZipFileInfo(ZipInfo zipInfo, string filePath)
|
||||
: base(zipInfo, filePath)
|
||||
{
|
||||
if (zipInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException("zipInfo");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ZipFileInfo object with all parameters specified,
|
||||
/// used internally when reading the metadata out of a zip archive.
|
||||
/// </summary>
|
||||
/// <param name="filePath">The internal path and name of the file in the zip archive.</param>
|
||||
/// <param name="zipNumber">The zip archive number where the file starts.</param>
|
||||
/// <param name="attributes">The stored attributes of the file.</param>
|
||||
/// <param name="lastWriteTime">The stored last write time of the file.</param>
|
||||
/// <param name="length">The uncompressed size of the file.</param>
|
||||
/// <param name="compressedLength">The compressed size of the file.</param>
|
||||
/// <param name="compressionMethod">Compression algorithm used for this file.</param>
|
||||
internal ZipFileInfo(
|
||||
string filePath,
|
||||
int zipNumber,
|
||||
FileAttributes attributes,
|
||||
DateTime lastWriteTime,
|
||||
long length,
|
||||
long compressedLength,
|
||||
ZipCompressionMethod compressionMethod)
|
||||
: base(filePath, zipNumber, attributes, lastWriteTime, length)
|
||||
{
|
||||
this.compressedLength = compressedLength;
|
||||
this.compressionMethod = compressionMethod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the compressed size of the file in bytes.
|
||||
/// </summary>
|
||||
public long CompressedLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.compressedLength;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the method used to compress this file.
|
||||
/// </summary>
|
||||
public ZipCompressionMethod CompressionMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.compressionMethod;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipFormat.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
[Flags]
|
||||
internal enum ZipFileFlags : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
Encrypt = 0x0001,
|
||||
CompressOption1 = 0x0002,
|
||||
CompressOption2 = 0x0004,
|
||||
DataDescriptor = 0x0008,
|
||||
StrongEncrypt = 0x0040,
|
||||
UTF8 = 0x0800
|
||||
}
|
||||
|
||||
internal enum ZipExtraFileFieldType : ushort
|
||||
{
|
||||
ZIP64 = 0x0001,
|
||||
NTFS_TIMES = 0x000A,
|
||||
NTFS_ACLS = 0x4453,
|
||||
EXTIME = 0x5455
|
||||
}
|
||||
|
||||
internal class ZipFileHeader
|
||||
{
|
||||
public const uint LFHSIG = 0x04034B50;
|
||||
public const uint CFHSIG = 0x02014B50;
|
||||
|
||||
public const uint SPANSIG = 0x08074b50;
|
||||
public const uint SPANSIG2 = 0x30304b50;
|
||||
|
||||
public const uint LFH_FIXEDSIZE = 30;
|
||||
public const uint CFH_FIXEDSIZE = 46;
|
||||
|
||||
public ushort versionMadeBy;
|
||||
public ushort versionNeeded;
|
||||
public ZipFileFlags flags;
|
||||
public ZipCompressionMethod compressionMethod;
|
||||
public short lastModTime;
|
||||
public short lastModDate;
|
||||
public uint crc32;
|
||||
public uint compressedSize;
|
||||
public uint uncompressedSize;
|
||||
public ushort diskStart;
|
||||
public ushort internalFileAttrs;
|
||||
public uint externalFileAttrs;
|
||||
public uint localHeaderOffset;
|
||||
public string fileName;
|
||||
public ZipExtraFileField[] extraFields;
|
||||
public string fileComment;
|
||||
public bool zip64;
|
||||
|
||||
public ZipFileHeader()
|
||||
{
|
||||
this.versionMadeBy = 20;
|
||||
this.versionNeeded = 20;
|
||||
}
|
||||
|
||||
public ZipFileHeader(ZipFileInfo fileInfo, bool zip64)
|
||||
: this()
|
||||
{
|
||||
this.flags = ZipFileFlags.None;
|
||||
this.compressionMethod = fileInfo.CompressionMethod;
|
||||
this.fileName = Path.Combine(fileInfo.Path, fileInfo.Name);
|
||||
CompressionEngine.DateTimeToDosDateAndTime(
|
||||
fileInfo.LastWriteTime, out this.lastModDate, out this.lastModTime);
|
||||
this.zip64 = zip64;
|
||||
|
||||
if (this.zip64)
|
||||
{
|
||||
this.compressedSize = UInt32.MaxValue;
|
||||
this.uncompressedSize = UInt32.MaxValue;
|
||||
this.diskStart = UInt16.MaxValue;
|
||||
this.versionMadeBy = 45;
|
||||
this.versionNeeded = 45;
|
||||
ZipExtraFileField field = new ZipExtraFileField();
|
||||
field.fieldType = ZipExtraFileFieldType.ZIP64;
|
||||
field.SetZip64Data(
|
||||
fileInfo.CompressedLength,
|
||||
fileInfo.Length,
|
||||
0,
|
||||
fileInfo.ArchiveNumber);
|
||||
this.extraFields = new ZipExtraFileField[] { field };
|
||||
}
|
||||
else
|
||||
{
|
||||
this.compressedSize = (uint) fileInfo.CompressedLength;
|
||||
this.uncompressedSize = (uint) fileInfo.Length;
|
||||
this.diskStart = (ushort) fileInfo.ArchiveNumber;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
|
||||
public void Update(
|
||||
long compressedSize,
|
||||
long uncompressedSize,
|
||||
uint crc32,
|
||||
long localHeaderOffset,
|
||||
int archiveNumber)
|
||||
{
|
||||
this.crc32 = crc32;
|
||||
|
||||
if (this.zip64)
|
||||
{
|
||||
this.compressedSize = UInt32.MaxValue;
|
||||
this.uncompressedSize = UInt32.MaxValue;
|
||||
this.localHeaderOffset = UInt32.MaxValue;
|
||||
this.diskStart = UInt16.MaxValue;
|
||||
|
||||
if (this.extraFields != null)
|
||||
{
|
||||
foreach (ZipExtraFileField field in this.extraFields)
|
||||
{
|
||||
if (field.fieldType == ZipExtraFileFieldType.ZIP64)
|
||||
{
|
||||
field.SetZip64Data(
|
||||
compressedSize,
|
||||
uncompressedSize,
|
||||
localHeaderOffset,
|
||||
archiveNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.compressedSize = (uint) compressedSize;
|
||||
this.uncompressedSize = (uint) uncompressedSize;
|
||||
this.localHeaderOffset = (uint) localHeaderOffset;
|
||||
this.diskStart = (ushort) archiveNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Read(Stream stream, bool central)
|
||||
{
|
||||
long startPos = stream.Position;
|
||||
|
||||
if (stream.Length - startPos <
|
||||
(central ? CFH_FIXEDSIZE : LFH_FIXEDSIZE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
uint sig = reader.ReadUInt32();
|
||||
|
||||
if (sig == SPANSIG || sig == SPANSIG2)
|
||||
{
|
||||
// Spanned zip files may optionally begin with a special marker.
|
||||
// Just ignore it and move on.
|
||||
sig = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
if (sig != (central ? CFHSIG : LFHSIG))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.versionMadeBy = (central ? reader.ReadUInt16() : (ushort) 0);
|
||||
this.versionNeeded = reader.ReadUInt16();
|
||||
this.flags = (ZipFileFlags) reader.ReadUInt16();
|
||||
this.compressionMethod = (ZipCompressionMethod) reader.ReadUInt16();
|
||||
this.lastModTime = reader.ReadInt16();
|
||||
this.lastModDate = reader.ReadInt16();
|
||||
this.crc32 = reader.ReadUInt32();
|
||||
this.compressedSize = reader.ReadUInt32();
|
||||
this.uncompressedSize = reader.ReadUInt32();
|
||||
|
||||
this.zip64 = this.uncompressedSize == UInt32.MaxValue;
|
||||
|
||||
int fileNameLength = reader.ReadUInt16();
|
||||
int extraFieldLength = reader.ReadUInt16();
|
||||
int fileCommentLength;
|
||||
|
||||
if (central)
|
||||
{
|
||||
fileCommentLength = reader.ReadUInt16();
|
||||
|
||||
this.diskStart = reader.ReadUInt16();
|
||||
this.internalFileAttrs = reader.ReadUInt16();
|
||||
this.externalFileAttrs = reader.ReadUInt32();
|
||||
this.localHeaderOffset = reader.ReadUInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
fileCommentLength = 0;
|
||||
this.diskStart = 0;
|
||||
this.internalFileAttrs = 0;
|
||||
this.externalFileAttrs = 0;
|
||||
this.localHeaderOffset = 0;
|
||||
}
|
||||
|
||||
if (stream.Length - stream.Position <
|
||||
fileNameLength + extraFieldLength + fileCommentLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if CORECLR
|
||||
Encoding headerEncoding = Encoding.UTF8;
|
||||
#else
|
||||
Encoding headerEncoding = ((this.flags | ZipFileFlags.UTF8) != 0 ?
|
||||
Encoding.UTF8 : Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage));
|
||||
#endif
|
||||
|
||||
byte[] fileNameBytes = reader.ReadBytes(fileNameLength);
|
||||
this.fileName = headerEncoding.GetString(fileNameBytes).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
List<ZipExtraFileField> fields = new List<ZipExtraFileField>();
|
||||
while (extraFieldLength > 0)
|
||||
{
|
||||
ZipExtraFileField field = new ZipExtraFileField();
|
||||
if (!field.Read(stream, ref extraFieldLength))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
fields.Add(field);
|
||||
if (field.fieldType == ZipExtraFileFieldType.ZIP64)
|
||||
{
|
||||
this.zip64 = true;
|
||||
}
|
||||
}
|
||||
this.extraFields = fields.ToArray();
|
||||
|
||||
byte[] fileCommentBytes = reader.ReadBytes(fileCommentLength);
|
||||
this.fileComment = headerEncoding.GetString(fileCommentBytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Write(Stream stream, bool central)
|
||||
{
|
||||
byte[] fileNameBytes = (this.fileName != null
|
||||
? Encoding.UTF8.GetBytes(this.fileName) : new byte[0]);
|
||||
byte[] fileCommentBytes = (this.fileComment != null
|
||||
? Encoding.UTF8.GetBytes(this.fileComment) : new byte[0]);
|
||||
bool useUtf8 =
|
||||
(this.fileName != null && fileNameBytes.Length > this.fileName.Length) ||
|
||||
(this.fileComment != null && fileCommentBytes.Length > this.fileComment.Length);
|
||||
if (useUtf8)
|
||||
{
|
||||
this.flags |= ZipFileFlags.UTF8;
|
||||
}
|
||||
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
writer.Write(central ? CFHSIG : LFHSIG);
|
||||
if (central)
|
||||
{
|
||||
writer.Write(this.versionMadeBy);
|
||||
}
|
||||
writer.Write(this.versionNeeded);
|
||||
writer.Write((ushort) this.flags);
|
||||
writer.Write((ushort) this.compressionMethod);
|
||||
writer.Write(this.lastModTime);
|
||||
writer.Write(this.lastModDate);
|
||||
writer.Write(this.crc32);
|
||||
writer.Write(this.compressedSize);
|
||||
writer.Write(this.uncompressedSize);
|
||||
|
||||
ushort extraFieldLength = 0;
|
||||
if (this.extraFields != null)
|
||||
{
|
||||
foreach (ZipExtraFileField field in this.extraFields)
|
||||
{
|
||||
if (field.data != null)
|
||||
{
|
||||
extraFieldLength += (ushort) (4 + field.data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writer.Write((ushort) fileNameBytes.Length);
|
||||
writer.Write(extraFieldLength);
|
||||
|
||||
if (central)
|
||||
{
|
||||
writer.Write((ushort) fileCommentBytes.Length);
|
||||
|
||||
writer.Write(this.diskStart);
|
||||
writer.Write(this.internalFileAttrs);
|
||||
writer.Write(this.externalFileAttrs);
|
||||
writer.Write(this.localHeaderOffset);
|
||||
}
|
||||
|
||||
writer.Write(fileNameBytes);
|
||||
|
||||
if (this.extraFields != null)
|
||||
{
|
||||
foreach (ZipExtraFileField field in this.extraFields)
|
||||
{
|
||||
if (field.data != null)
|
||||
{
|
||||
field.Write(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (central)
|
||||
{
|
||||
writer.Write(fileCommentBytes);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "compressedSize")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "uncompressedSize")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "crc32")]
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "localHeaderOffset")]
|
||||
public void GetZip64Fields(
|
||||
out long compressedSize,
|
||||
out long uncompressedSize,
|
||||
out long localHeaderOffset,
|
||||
out int archiveNumber,
|
||||
out uint crc)
|
||||
{
|
||||
compressedSize = this.compressedSize;
|
||||
uncompressedSize = this.uncompressedSize;
|
||||
localHeaderOffset = this.localHeaderOffset;
|
||||
archiveNumber = this.diskStart;
|
||||
crc = this.crc32;
|
||||
|
||||
foreach (ZipExtraFileField field in this.extraFields)
|
||||
{
|
||||
if (field.fieldType == ZipExtraFileFieldType.ZIP64)
|
||||
{
|
||||
field.GetZip64Data(
|
||||
out compressedSize,
|
||||
out uncompressedSize,
|
||||
out localHeaderOffset,
|
||||
out archiveNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ZipFileInfo ToZipFileInfo()
|
||||
{
|
||||
string name = this.fileName;
|
||||
|
||||
long compressedSizeL;
|
||||
long uncompressedSizeL;
|
||||
long localHeaderOffsetL;
|
||||
int archiveNumberL;
|
||||
uint crc;
|
||||
this.GetZip64Fields(
|
||||
out compressedSizeL,
|
||||
out uncompressedSizeL,
|
||||
out localHeaderOffsetL,
|
||||
out archiveNumberL,
|
||||
out crc);
|
||||
|
||||
DateTime dateTime;
|
||||
CompressionEngine.DosDateAndTimeToDateTime(
|
||||
this.lastModDate,
|
||||
this.lastModTime,
|
||||
out dateTime);
|
||||
FileAttributes attrs = FileAttributes.Normal;
|
||||
// TODO: look for attrs or times in extra fields
|
||||
|
||||
return new ZipFileInfo(name, archiveNumberL, attrs, dateTime,
|
||||
uncompressedSizeL, compressedSizeL, this.compressionMethod);
|
||||
}
|
||||
|
||||
public bool IsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fileName != null &&
|
||||
(this.fileName.EndsWith("/", StringComparison.Ordinal) ||
|
||||
this.fileName.EndsWith("\\", StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSize(bool central)
|
||||
{
|
||||
int size = 30;
|
||||
|
||||
int fileNameSize = (this.fileName != null
|
||||
? Encoding.UTF8.GetByteCount(this.fileName) : 0);
|
||||
size += fileNameSize;
|
||||
|
||||
if (this.extraFields != null)
|
||||
{
|
||||
foreach (ZipExtraFileField field in this.extraFields)
|
||||
{
|
||||
if (field.data != null)
|
||||
{
|
||||
size += 4 + field.data.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (central)
|
||||
{
|
||||
size += 16;
|
||||
|
||||
int fileCommentSize = (this.fileComment != null
|
||||
? Encoding.UTF8.GetByteCount(this.fileComment) : 0);
|
||||
size += fileCommentSize;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ZipExtraFileField
|
||||
{
|
||||
public ZipExtraFileFieldType fieldType;
|
||||
public byte[] data;
|
||||
|
||||
public bool Read(Stream stream, ref int bytesRemaining)
|
||||
{
|
||||
if (bytesRemaining < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
|
||||
this.fieldType = (ZipExtraFileFieldType) reader.ReadUInt16();
|
||||
ushort dataSize = reader.ReadUInt16();
|
||||
bytesRemaining -= 4;
|
||||
|
||||
if (bytesRemaining < dataSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.data = reader.ReadBytes(dataSize);
|
||||
bytesRemaining -= dataSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Write(Stream stream)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
writer.Write((ushort) this.fieldType);
|
||||
|
||||
byte[] dataBytes = (this.data != null ? this.data : new byte[0]);
|
||||
writer.Write((ushort) dataBytes.Length);
|
||||
writer.Write(dataBytes);
|
||||
}
|
||||
|
||||
public bool GetZip64Data(
|
||||
out long compressedSize,
|
||||
out long uncompressedSize,
|
||||
out long localHeaderOffset,
|
||||
out int diskStart)
|
||||
{
|
||||
uncompressedSize = 0;
|
||||
compressedSize = 0;
|
||||
localHeaderOffset = 0;
|
||||
diskStart = 0;
|
||||
|
||||
if (this.fieldType != ZipExtraFileFieldType.ZIP64 ||
|
||||
this.data == null || this.data.Length != 28)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MemoryStream dataStream = new MemoryStream(this.data))
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(dataStream);
|
||||
uncompressedSize = reader.ReadInt64();
|
||||
compressedSize = reader.ReadInt64();
|
||||
localHeaderOffset = reader.ReadInt64();
|
||||
diskStart = reader.ReadInt32();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetZip64Data(
|
||||
long compressedSize,
|
||||
long uncompressedSize,
|
||||
long localHeaderOffset,
|
||||
int diskStart)
|
||||
{
|
||||
if (this.fieldType != ZipExtraFileFieldType.ZIP64)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using (MemoryStream dataStream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(dataStream);
|
||||
writer.Write(uncompressedSize);
|
||||
writer.Write(compressedSize);
|
||||
writer.Write(localHeaderOffset);
|
||||
writer.Write(diskStart);
|
||||
this.data = dataStream.ToArray();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ZipEndOfCentralDirectory
|
||||
{
|
||||
public const uint EOCDSIG = 0x06054B50;
|
||||
public const uint EOCD64SIG = 0x06064B50;
|
||||
|
||||
public const uint EOCD_RECORD_FIXEDSIZE = 22;
|
||||
public const uint EOCD64_RECORD_FIXEDSIZE = 56;
|
||||
|
||||
public ushort versionMadeBy;
|
||||
public ushort versionNeeded;
|
||||
public uint diskNumber;
|
||||
public uint dirStartDiskNumber;
|
||||
public long entriesOnDisk;
|
||||
public long totalEntries;
|
||||
public long dirSize;
|
||||
public long dirOffset;
|
||||
public string comment;
|
||||
public bool zip64;
|
||||
|
||||
public ZipEndOfCentralDirectory()
|
||||
{
|
||||
this.versionMadeBy = 20;
|
||||
this.versionNeeded = 20;
|
||||
}
|
||||
|
||||
public bool Read(Stream stream)
|
||||
{
|
||||
long startPos = stream.Position;
|
||||
|
||||
if (stream.Length - startPos < EOCD_RECORD_FIXEDSIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
uint sig = reader.ReadUInt32();
|
||||
|
||||
this.zip64 = false;
|
||||
if (sig != EOCDSIG)
|
||||
{
|
||||
if (sig == EOCD64SIG)
|
||||
{
|
||||
this.zip64 = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.zip64)
|
||||
{
|
||||
if (stream.Length - startPos < EOCD64_RECORD_FIXEDSIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
long recordSize = reader.ReadInt64();
|
||||
this.versionMadeBy = reader.ReadUInt16();
|
||||
this.versionNeeded = reader.ReadUInt16();
|
||||
this.diskNumber = reader.ReadUInt32();
|
||||
this.dirStartDiskNumber = reader.ReadUInt32();
|
||||
this.entriesOnDisk = reader.ReadInt64();
|
||||
this.totalEntries = reader.ReadInt64();
|
||||
this.dirSize = reader.ReadInt64();
|
||||
this.dirOffset = reader.ReadInt64();
|
||||
|
||||
// Ignore any extended zip64 eocd data.
|
||||
long exDataSize = recordSize + 12 - EOCD64_RECORD_FIXEDSIZE;
|
||||
|
||||
if (stream.Length - stream.Position < exDataSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream.Seek(exDataSize, SeekOrigin.Current);
|
||||
|
||||
this.comment = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.diskNumber = reader.ReadUInt16();
|
||||
this.dirStartDiskNumber = reader.ReadUInt16();
|
||||
this.entriesOnDisk = reader.ReadUInt16();
|
||||
this.totalEntries = reader.ReadUInt16();
|
||||
this.dirSize = reader.ReadUInt32();
|
||||
this.dirOffset = reader.ReadUInt32();
|
||||
|
||||
int commentLength = reader.ReadUInt16();
|
||||
|
||||
if (stream.Length - stream.Position < commentLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] commentBytes = reader.ReadBytes(commentLength);
|
||||
this.comment = Encoding.UTF8.GetString(commentBytes);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Write(Stream stream)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
|
||||
if (this.zip64)
|
||||
{
|
||||
writer.Write(EOCD64SIG);
|
||||
writer.Write((long) EOCD64_RECORD_FIXEDSIZE);
|
||||
writer.Write(this.versionMadeBy);
|
||||
writer.Write(this.versionNeeded);
|
||||
writer.Write(this.diskNumber);
|
||||
writer.Write(this.dirStartDiskNumber);
|
||||
writer.Write(this.entriesOnDisk);
|
||||
writer.Write(this.totalEntries);
|
||||
writer.Write(this.dirSize);
|
||||
writer.Write(this.dirOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Write(EOCDSIG);
|
||||
writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.diskNumber));
|
||||
writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.dirStartDiskNumber));
|
||||
writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.entriesOnDisk));
|
||||
writer.Write((ushort) Math.Min((int) UInt16.MaxValue, this.totalEntries));
|
||||
writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirSize));
|
||||
writer.Write((uint) Math.Min((long) UInt32.MaxValue, this.dirOffset));
|
||||
|
||||
byte[] commentBytes = (this.comment != null
|
||||
? Encoding.UTF8.GetBytes(this.comment) : new byte[0]);
|
||||
writer.Write((ushort) commentBytes.Length);
|
||||
writer.Write(commentBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetSize(bool zip64Size)
|
||||
{
|
||||
if (zip64Size)
|
||||
{
|
||||
return 56;
|
||||
}
|
||||
else
|
||||
{
|
||||
int commentSize = (this.comment != null
|
||||
? Encoding.UTF8.GetByteCount(this.comment) : 0);
|
||||
return 22 + commentSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Zip64EndOfCentralDirectoryLocator
|
||||
{
|
||||
public const uint EOCDL64SIG = 0x07064B50;
|
||||
|
||||
public const uint EOCDL64_SIZE = 20;
|
||||
|
||||
public uint dirStartDiskNumber;
|
||||
public long dirOffset;
|
||||
public uint totalDisks;
|
||||
|
||||
public bool Read(Stream stream)
|
||||
{
|
||||
long startPos = stream.Position;
|
||||
if (stream.Length - startPos < EOCDL64_SIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
uint sig = reader.ReadUInt32();
|
||||
|
||||
if (sig != EOCDL64SIG)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this.dirStartDiskNumber = reader.ReadUInt32();
|
||||
this.dirOffset = reader.ReadInt64();
|
||||
this.totalDisks = reader.ReadUInt32();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Write(Stream stream)
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
writer.Write(EOCDL64SIG);
|
||||
writer.Write(this.dirStartDiskNumber);
|
||||
writer.Write(this.dirOffset);
|
||||
writer.Write(this.totalDisks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Object representing a zip file on disk; provides access to
|
||||
/// file-based operations on the zip file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally, the methods on this class are much easier to use than the
|
||||
/// stream-based interfaces provided by the <see cref="ZipEngine"/> class.
|
||||
/// </remarks>
|
||||
public class ZipInfo : ArchiveInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new CabinetInfo object representing a zip file in a specified path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the zip file. When creating a zip file, this file does not
|
||||
/// necessarily exist yet.</param>
|
||||
public ZipInfo(string path)
|
||||
: base(path)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a compression engine that does the low-level work for
|
||||
/// this object.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="ZipEngine"/> instance.</returns>
|
||||
/// <remarks>
|
||||
/// Each instance will be <see cref="CompressionEngine.Dispose()"/>d
|
||||
/// immediately after use.
|
||||
/// </remarks>
|
||||
protected override CompressionEngine CreateCompressionEngine()
|
||||
{
|
||||
return new ZipEngine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the files contained in the archive.
|
||||
/// </summary>
|
||||
/// <returns>A list of <see cref="ZipFileInfo"/> objects, each
|
||||
/// containing information about a file in the archive.</returns>
|
||||
public new IList<ZipFileInfo> GetFiles()
|
||||
{
|
||||
IList<ArchiveFileInfo> files = base.GetFiles();
|
||||
List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
|
||||
foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
|
||||
return zipFiles.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the certain files contained in the archive file.
|
||||
/// </summary>
|
||||
/// <param name="searchPattern">The search string, such as
|
||||
/// "*.txt".</param>
|
||||
/// <returns>A list of <see cref="ZipFileInfo"/> objects, each containing
|
||||
/// information about a file in the archive.</returns>
|
||||
public new IList<ZipFileInfo> GetFiles(string searchPattern)
|
||||
{
|
||||
IList<ArchiveFileInfo> files = base.GetFiles(searchPattern);
|
||||
List<ZipFileInfo> zipFiles = new List<ZipFileInfo>(files.Count);
|
||||
foreach (ZipFileInfo zipFile in files) zipFiles.Add(zipFile);
|
||||
return zipFiles.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,501 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipPacker.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using CompressionLevel = Compression.CompressionLevel;
|
||||
|
||||
public partial class ZipEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a zip archive or chain of zip archives.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="files">An array of file lists. Each list is
|
||||
/// compressed into one stream in the archive.</param>
|
||||
/// <param name="maxArchiveSize">The maximum number of bytes for one archive
|
||||
/// before the contents are chained to the next archive, or zero for unlimited
|
||||
/// archive size.</param>
|
||||
/// <exception cref="ArchiveException">The archive could not be
|
||||
/// created.</exception>
|
||||
/// <remarks>
|
||||
/// The stream context implementation may provide a mapping from the file
|
||||
/// paths within the archive to the external file paths.
|
||||
/// </remarks>
|
||||
public override void Pack(
|
||||
IPackStreamContext streamContext,
|
||||
IEnumerable<string> files,
|
||||
long maxArchiveSize)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
if (files == null)
|
||||
{
|
||||
throw new ArgumentNullException("files");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
Stream archiveStream = null;
|
||||
try
|
||||
{
|
||||
this.ResetProgressData();
|
||||
this.totalArchives = 1;
|
||||
|
||||
object forceZip64Value = streamContext.GetOption("forceZip64", null);
|
||||
bool forceZip64 = Convert.ToBoolean(
|
||||
forceZip64Value, CultureInfo.InvariantCulture);
|
||||
|
||||
// Count the total number of files and bytes to be compressed.
|
||||
foreach (string file in files)
|
||||
{
|
||||
FileAttributes attributes;
|
||||
DateTime lastWriteTime;
|
||||
Stream fileStream = streamContext.OpenFileReadStream(
|
||||
file,
|
||||
out attributes,
|
||||
out lastWriteTime);
|
||||
if (fileStream != null)
|
||||
{
|
||||
this.totalFileBytes += fileStream.Length;
|
||||
this.totalFiles++;
|
||||
streamContext.CloseFileReadStream(file, fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
List<ZipFileHeader> fileHeaders = new List<ZipFileHeader>();
|
||||
this.currentFileNumber = -1;
|
||||
|
||||
if (this.currentArchiveName == null)
|
||||
{
|
||||
this.mainArchiveName = streamContext.GetArchiveName(0);
|
||||
this.currentArchiveName = this.mainArchiveName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(this.currentArchiveName))
|
||||
{
|
||||
throw new FileNotFoundException("No name provided for archive.");
|
||||
}
|
||||
}
|
||||
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
|
||||
// Compress files one by one, saving header info for each.
|
||||
foreach (string file in files)
|
||||
{
|
||||
ZipFileHeader fileHeader = this.PackOneFile(
|
||||
streamContext,
|
||||
file,
|
||||
maxArchiveSize,
|
||||
forceZip64,
|
||||
ref archiveStream);
|
||||
|
||||
if (fileHeader != null)
|
||||
{
|
||||
fileHeaders.Add(fileHeader);
|
||||
}
|
||||
|
||||
this.currentArchiveTotalBytes = (archiveStream != null ?
|
||||
archiveStream.Position : 0);
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
}
|
||||
|
||||
bool zip64 = forceZip64 || this.totalFiles > UInt16.MaxValue;
|
||||
|
||||
// Write the central directory composed of all the file headers.
|
||||
uint centralDirStartArchiveNumber = 0;
|
||||
long centralDirStartPosition = 0;
|
||||
long centralDirSize = 0;
|
||||
for (int i = 0; i < fileHeaders.Count; i++)
|
||||
{
|
||||
ZipFileHeader fileHeader = fileHeaders[i];
|
||||
|
||||
int headerSize = fileHeader.GetSize(true);
|
||||
centralDirSize += headerSize;
|
||||
|
||||
this.CheckArchiveWriteStream(
|
||||
streamContext,
|
||||
maxArchiveSize,
|
||||
headerSize,
|
||||
ref archiveStream);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
centralDirStartArchiveNumber = (uint)this.currentArchiveNumber;
|
||||
centralDirStartPosition = archiveStream.Position;
|
||||
}
|
||||
|
||||
fileHeader.Write(archiveStream, true);
|
||||
if (fileHeader.zip64)
|
||||
{
|
||||
zip64 = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.currentArchiveTotalBytes =
|
||||
(archiveStream != null ? archiveStream.Position : 0);
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
|
||||
ZipEndOfCentralDirectory eocd = new ZipEndOfCentralDirectory();
|
||||
eocd.dirStartDiskNumber = centralDirStartArchiveNumber;
|
||||
eocd.entriesOnDisk = fileHeaders.Count;
|
||||
eocd.totalEntries = fileHeaders.Count;
|
||||
eocd.dirSize = centralDirSize;
|
||||
eocd.dirOffset = centralDirStartPosition;
|
||||
eocd.comment = this.comment;
|
||||
|
||||
Zip64EndOfCentralDirectoryLocator eocdl =
|
||||
new Zip64EndOfCentralDirectoryLocator();
|
||||
|
||||
int maxFooterSize = eocd.GetSize(false);
|
||||
if (archiveStream != null && (zip64 || archiveStream.Position >
|
||||
((long)UInt32.MaxValue) - eocd.GetSize(false)))
|
||||
{
|
||||
maxFooterSize += eocd.GetSize(true) + (int)
|
||||
Zip64EndOfCentralDirectoryLocator.EOCDL64_SIZE;
|
||||
zip64 = true;
|
||||
}
|
||||
|
||||
this.CheckArchiveWriteStream(
|
||||
streamContext,
|
||||
maxArchiveSize,
|
||||
maxFooterSize,
|
||||
ref archiveStream);
|
||||
eocd.diskNumber = (uint)this.currentArchiveNumber;
|
||||
|
||||
if (zip64)
|
||||
{
|
||||
eocd.versionMadeBy = 45;
|
||||
eocd.versionNeeded = 45;
|
||||
eocd.zip64 = true;
|
||||
eocdl.dirOffset = archiveStream.Position;
|
||||
eocdl.dirStartDiskNumber = (uint)this.currentArchiveNumber;
|
||||
eocdl.totalDisks = (uint)this.currentArchiveNumber + 1;
|
||||
eocd.Write(archiveStream);
|
||||
eocdl.Write(archiveStream);
|
||||
|
||||
eocd.dirOffset = UInt32.MaxValue;
|
||||
eocd.dirStartDiskNumber = UInt16.MaxValue;
|
||||
}
|
||||
|
||||
eocd.zip64 = false;
|
||||
eocd.Write(archiveStream);
|
||||
|
||||
this.currentArchiveTotalBytes = archiveStream.Position;
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (archiveStream != null)
|
||||
{
|
||||
streamContext.CloseArchiveWriteStream(
|
||||
this.currentArchiveNumber, this.mainArchiveName, archiveStream);
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next archive in the sequence if necessary.
|
||||
/// </summary>
|
||||
private void CheckArchiveWriteStream(
|
||||
IPackStreamContext streamContext,
|
||||
long maxArchiveSize,
|
||||
long requiredSize,
|
||||
ref Stream archiveStream)
|
||||
{
|
||||
if (archiveStream != null &&
|
||||
archiveStream.Length > 0 && maxArchiveSize > 0)
|
||||
{
|
||||
long sizeRemaining = maxArchiveSize - archiveStream.Length;
|
||||
if (sizeRemaining < requiredSize)
|
||||
{
|
||||
string nextArchiveName = streamContext.GetArchiveName(
|
||||
this.currentArchiveNumber + 1);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(nextArchiveName))
|
||||
{
|
||||
throw new FileNotFoundException("No name provided for archive #" +
|
||||
this.currentArchiveNumber + 1);
|
||||
}
|
||||
|
||||
this.currentArchiveTotalBytes = archiveStream.Position;
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
|
||||
streamContext.CloseArchiveWriteStream(
|
||||
this.currentArchiveNumber,
|
||||
nextArchiveName,
|
||||
archiveStream);
|
||||
archiveStream = null;
|
||||
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
|
||||
this.currentArchiveNumber++;
|
||||
this.totalArchives++;
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
this.currentArchiveTotalBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (archiveStream == null)
|
||||
{
|
||||
if (this.currentArchiveNumber > 0)
|
||||
{
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
}
|
||||
|
||||
archiveStream = streamContext.OpenArchiveWriteStream(
|
||||
this.currentArchiveNumber, this.mainArchiveName, true, this);
|
||||
|
||||
if (archiveStream == null)
|
||||
{
|
||||
throw new FileNotFoundException("Stream not provided for archive #" +
|
||||
this.currentArchiveNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds one file to a zip archive in the process of being created.
|
||||
/// </summary>
|
||||
private ZipFileHeader PackOneFile(
|
||||
IPackStreamContext streamContext,
|
||||
string file,
|
||||
long maxArchiveSize,
|
||||
bool forceZip64,
|
||||
ref Stream archiveStream)
|
||||
{
|
||||
Stream fileStream = null;
|
||||
int headerArchiveNumber = 0;
|
||||
try
|
||||
{
|
||||
// TODO: call GetOption to get compression method for the specific file
|
||||
ZipCompressionMethod compressionMethod = ZipCompressionMethod.Deflate;
|
||||
if (this.CompressionLevel == CompressionLevel.None)
|
||||
{
|
||||
compressionMethod = ZipCompressionMethod.Store;
|
||||
}
|
||||
|
||||
Func<Stream, Stream> compressionStreamCreator;
|
||||
if (!ZipEngine.compressionStreamCreators.TryGetValue(
|
||||
compressionMethod, out compressionStreamCreator))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileAttributes attributes;
|
||||
DateTime lastWriteTime;
|
||||
fileStream = streamContext.OpenFileReadStream(
|
||||
file, out attributes, out lastWriteTime);
|
||||
if (fileStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
this.currentFileName = file;
|
||||
this.currentFileNumber++;
|
||||
|
||||
this.currentFileTotalBytes = fileStream.Length;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.OnProgress(ArchiveProgressType.StartFile);
|
||||
|
||||
ZipFileInfo fileInfo = new ZipFileInfo(
|
||||
file,
|
||||
this.currentArchiveNumber,
|
||||
attributes,
|
||||
lastWriteTime,
|
||||
fileStream.Length,
|
||||
0,
|
||||
compressionMethod);
|
||||
|
||||
bool zip64 = forceZip64 || fileStream.Length >= (long)UInt32.MaxValue;
|
||||
ZipFileHeader fileHeader = new ZipFileHeader(fileInfo, zip64);
|
||||
|
||||
this.CheckArchiveWriteStream(
|
||||
streamContext,
|
||||
maxArchiveSize,
|
||||
fileHeader.GetSize(false),
|
||||
ref archiveStream);
|
||||
|
||||
long headerPosition = archiveStream.Position;
|
||||
fileHeader.Write(archiveStream, false);
|
||||
headerArchiveNumber = this.currentArchiveNumber;
|
||||
|
||||
uint crc;
|
||||
long bytesWritten = this.PackFileBytes(
|
||||
streamContext,
|
||||
fileStream,
|
||||
maxArchiveSize,
|
||||
compressionStreamCreator,
|
||||
ref archiveStream,
|
||||
out crc);
|
||||
|
||||
fileHeader.Update(
|
||||
bytesWritten,
|
||||
fileStream.Length,
|
||||
crc,
|
||||
headerPosition,
|
||||
headerArchiveNumber);
|
||||
|
||||
streamContext.CloseFileReadStream(file, fileStream);
|
||||
fileStream = null;
|
||||
|
||||
// Go back and rewrite the updated file header.
|
||||
if (this.currentArchiveNumber == headerArchiveNumber)
|
||||
{
|
||||
long fileEndPosition = archiveStream.Position;
|
||||
archiveStream.Seek(headerPosition, SeekOrigin.Begin);
|
||||
fileHeader.Write(archiveStream, false);
|
||||
archiveStream.Seek(fileEndPosition, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The file spanned archives, so temporarily reopen
|
||||
// the archive where it started.
|
||||
string headerArchiveName = streamContext.GetArchiveName(
|
||||
headerArchiveNumber + 1);
|
||||
Stream headerStream = null;
|
||||
try
|
||||
{
|
||||
headerStream = streamContext.OpenArchiveWriteStream(
|
||||
headerArchiveNumber, headerArchiveName, false, this);
|
||||
headerStream.Seek(headerPosition, SeekOrigin.Begin);
|
||||
fileHeader.Write(headerStream, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (headerStream != null)
|
||||
{
|
||||
streamContext.CloseArchiveWriteStream(
|
||||
headerArchiveNumber, headerArchiveName, headerStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.OnProgress(ArchiveProgressType.FinishFile);
|
||||
|
||||
return fileHeader;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fileStream != null)
|
||||
{
|
||||
streamContext.CloseFileReadStream(
|
||||
this.currentFileName, fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes compressed bytes of one file to the archive,
|
||||
/// keeping track of the CRC and number of bytes written.
|
||||
/// </summary>
|
||||
private long PackFileBytes(
|
||||
IPackStreamContext streamContext,
|
||||
Stream fileStream,
|
||||
long maxArchiveSize,
|
||||
Func<Stream, Stream> compressionStreamCreator,
|
||||
ref Stream archiveStream,
|
||||
out uint crc)
|
||||
{
|
||||
long writeStartPosition = archiveStream.Position;
|
||||
long bytesWritten = 0;
|
||||
CrcStream fileCrcStream = new CrcStream(fileStream);
|
||||
|
||||
ConcatStream concatStream = new ConcatStream(
|
||||
delegate(ConcatStream s)
|
||||
{
|
||||
Stream sourceStream = s.Source;
|
||||
bytesWritten += sourceStream.Position - writeStartPosition;
|
||||
|
||||
this.CheckArchiveWriteStream(
|
||||
streamContext,
|
||||
maxArchiveSize,
|
||||
1,
|
||||
ref sourceStream);
|
||||
|
||||
writeStartPosition = sourceStream.Position;
|
||||
s.Source = sourceStream;
|
||||
});
|
||||
|
||||
concatStream.Source = archiveStream;
|
||||
|
||||
if (maxArchiveSize > 0)
|
||||
{
|
||||
concatStream.SetLength(maxArchiveSize);
|
||||
}
|
||||
|
||||
Stream compressionStream = compressionStreamCreator(concatStream);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] buf = new byte[4096];
|
||||
long bytesRemaining = fileStream.Length;
|
||||
int counter = 0;
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
int count = (int)Math.Min(
|
||||
bytesRemaining, (long)buf.Length);
|
||||
|
||||
count = fileCrcStream.Read(buf, 0, count);
|
||||
if (count <= 0)
|
||||
{
|
||||
throw new ZipException(
|
||||
"Failed to read file: " + this.currentFileName);
|
||||
}
|
||||
|
||||
compressionStream.Write(buf, 0, count);
|
||||
bytesRemaining -= count;
|
||||
|
||||
this.fileBytesProcessed += count;
|
||||
this.currentFileBytesProcessed += count;
|
||||
this.currentArchiveTotalBytes = concatStream.Source.Position;
|
||||
this.currentArchiveBytesProcessed = this.currentArchiveTotalBytes;
|
||||
|
||||
if (++counter % 16 == 0) // Report every 64K
|
||||
{
|
||||
this.OnProgress(ArchiveProgressType.PartialFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (compressionStream is DeflateStream)
|
||||
{
|
||||
#if CORECLR
|
||||
compressionStream.Dispose();
|
||||
#else
|
||||
compressionStream.Close();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
compressionStream.Flush();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
archiveStream = concatStream.Source;
|
||||
}
|
||||
|
||||
bytesWritten += archiveStream.Position - writeStartPosition;
|
||||
|
||||
crc = fileCrcStream.Crc;
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ZipUnpacker.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal.Compression.Zip
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
public partial class ZipEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts files from a zip archive or archive chain.
|
||||
/// </summary>
|
||||
/// <param name="streamContext">A context interface to handle opening
|
||||
/// and closing of archive and file streams.</param>
|
||||
/// <param name="fileFilter">An optional predicate that can determine
|
||||
/// which files to process.</param>
|
||||
/// <exception cref="ArchiveException">The archive provided
|
||||
/// by the stream context is not valid.</exception>
|
||||
/// <remarks>
|
||||
/// The <paramref name="fileFilter"/> predicate takes an internal file
|
||||
/// path and returns true to include the file or false to exclude it.
|
||||
/// </remarks>
|
||||
public override void Unpack(
|
||||
IUnpackStreamContext streamContext,
|
||||
Predicate<string> fileFilter)
|
||||
{
|
||||
if (streamContext == null)
|
||||
{
|
||||
throw new ArgumentNullException("streamContext");
|
||||
}
|
||||
|
||||
lock (this)
|
||||
{
|
||||
IList<ZipFileHeader> allHeaders = this.GetCentralDirectory(streamContext);
|
||||
if (allHeaders == null)
|
||||
{
|
||||
throw new ZipException("Zip central directory not found.");
|
||||
}
|
||||
|
||||
IList<ZipFileHeader> headers = new List<ZipFileHeader>(allHeaders.Count);
|
||||
foreach (ZipFileHeader header in allHeaders)
|
||||
{
|
||||
if (!header.IsDirectory &&
|
||||
(fileFilter == null || fileFilter(header.fileName)))
|
||||
{
|
||||
headers.Add(header);
|
||||
}
|
||||
}
|
||||
|
||||
this.ResetProgressData();
|
||||
|
||||
// Count the total number of files and bytes to be compressed.
|
||||
this.totalFiles = headers.Count;
|
||||
foreach (ZipFileHeader header in headers)
|
||||
{
|
||||
long compressedSize;
|
||||
long uncompressedSize;
|
||||
long localHeaderOffset;
|
||||
int archiveNumber;
|
||||
uint crc;
|
||||
header.GetZip64Fields(
|
||||
out compressedSize,
|
||||
out uncompressedSize,
|
||||
out localHeaderOffset,
|
||||
out archiveNumber,
|
||||
out crc);
|
||||
|
||||
this.totalFileBytes += uncompressedSize;
|
||||
if (archiveNumber >= this.totalArchives)
|
||||
{
|
||||
this.totalArchives = (short)(archiveNumber + 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.currentArchiveNumber = -1;
|
||||
this.currentFileNumber = -1;
|
||||
Stream archiveStream = null;
|
||||
try
|
||||
{
|
||||
foreach (ZipFileHeader header in headers)
|
||||
{
|
||||
this.currentFileNumber++;
|
||||
this.UnpackOneFile(streamContext, header, ref archiveStream);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (archiveStream != null)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
0, String.Empty, archiveStream);
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks a single file from an archive or archive chain.
|
||||
/// </summary>
|
||||
private void UnpackOneFile(
|
||||
IUnpackStreamContext streamContext,
|
||||
ZipFileHeader header,
|
||||
ref Stream archiveStream)
|
||||
{
|
||||
ZipFileInfo fileInfo = null;
|
||||
Stream fileStream = null;
|
||||
try
|
||||
{
|
||||
Func<Stream, Stream> compressionStreamCreator;
|
||||
if (!ZipEngine.decompressionStreamCreators.TryGetValue(
|
||||
header.compressionMethod, out compressionStreamCreator))
|
||||
{
|
||||
// Silently skip files of an unsupported compression method.
|
||||
return;
|
||||
}
|
||||
|
||||
long compressedSize;
|
||||
long uncompressedSize;
|
||||
long localHeaderOffset;
|
||||
int archiveNumber;
|
||||
uint crc;
|
||||
header.GetZip64Fields(
|
||||
out compressedSize,
|
||||
out uncompressedSize,
|
||||
out localHeaderOffset,
|
||||
out archiveNumber,
|
||||
out crc);
|
||||
|
||||
if (this.currentArchiveNumber != archiveNumber + 1)
|
||||
{
|
||||
if (archiveStream != null)
|
||||
{
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber,
|
||||
String.Empty,
|
||||
archiveStream);
|
||||
archiveStream = null;
|
||||
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
this.currentArchiveName = null;
|
||||
}
|
||||
|
||||
this.currentArchiveNumber = (short)(archiveNumber + 1);
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
this.currentArchiveTotalBytes = 0;
|
||||
|
||||
archiveStream = this.OpenArchive(
|
||||
streamContext, this.currentArchiveNumber);
|
||||
|
||||
FileStream archiveFileStream = archiveStream as FileStream;
|
||||
this.currentArchiveName = (archiveFileStream != null ?
|
||||
Path.GetFileName(archiveFileStream.Name) : null);
|
||||
|
||||
this.currentArchiveTotalBytes = archiveStream.Length;
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
this.currentArchiveNumber++;
|
||||
}
|
||||
|
||||
archiveStream.Seek(localHeaderOffset, SeekOrigin.Begin);
|
||||
|
||||
ZipFileHeader localHeader = new ZipFileHeader();
|
||||
if (!localHeader.Read(archiveStream, false) ||
|
||||
!ZipEngine.AreFilePathsEqual(localHeader.fileName, header.fileName))
|
||||
{
|
||||
string msg = "Could not read file: " + header.fileName;
|
||||
throw new ZipException(msg);
|
||||
}
|
||||
|
||||
fileInfo = header.ToZipFileInfo();
|
||||
|
||||
fileStream = streamContext.OpenFileWriteStream(
|
||||
fileInfo.FullName,
|
||||
fileInfo.Length,
|
||||
fileInfo.LastWriteTime);
|
||||
|
||||
if (fileStream != null)
|
||||
{
|
||||
this.currentFileName = header.fileName;
|
||||
this.currentFileBytesProcessed = 0;
|
||||
this.currentFileTotalBytes = fileInfo.Length;
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.StartFile);
|
||||
this.currentArchiveNumber++;
|
||||
|
||||
this.UnpackFileBytes(
|
||||
streamContext,
|
||||
fileInfo.FullName,
|
||||
fileInfo.CompressedLength,
|
||||
fileInfo.Length,
|
||||
header.crc32,
|
||||
fileStream,
|
||||
compressionStreamCreator,
|
||||
ref archiveStream);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fileStream != null)
|
||||
{
|
||||
streamContext.CloseFileWriteStream(
|
||||
fileInfo.FullName,
|
||||
fileStream,
|
||||
fileInfo.Attributes,
|
||||
fileInfo.LastWriteTime);
|
||||
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.FinishFile);
|
||||
this.currentArchiveNumber++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two internal file paths while ignoring case and slash differences.
|
||||
/// </summary>
|
||||
/// <param name="path1">The first path to compare.</param>
|
||||
/// <param name="path2">The second path to compare.</param>
|
||||
/// <returns>True if the paths are equivalent.</returns>
|
||||
private static bool AreFilePathsEqual(string path1, string path2)
|
||||
{
|
||||
path1 = path1.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
path2 = path2.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
return String.Compare(path1, path2, StringComparison.OrdinalIgnoreCase) == 0;
|
||||
}
|
||||
|
||||
private Stream OpenArchive(IUnpackStreamContext streamContext, int archiveNumber)
|
||||
{
|
||||
Stream archiveStream = streamContext.OpenArchiveReadStream(
|
||||
archiveNumber, String.Empty, this);
|
||||
if (archiveStream == null && archiveNumber != 0)
|
||||
{
|
||||
archiveStream = streamContext.OpenArchiveReadStream(
|
||||
0, String.Empty, this);
|
||||
}
|
||||
|
||||
if (archiveStream == null)
|
||||
{
|
||||
throw new FileNotFoundException("Archive stream not provided.");
|
||||
}
|
||||
|
||||
return archiveStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decompresses bytes for one file from an archive or archive chain,
|
||||
/// checking the crc at the end.
|
||||
/// </summary>
|
||||
private void UnpackFileBytes(
|
||||
IUnpackStreamContext streamContext,
|
||||
string fileName,
|
||||
long compressedSize,
|
||||
long uncompressedSize,
|
||||
uint crc,
|
||||
Stream fileStream,
|
||||
Func<Stream, Stream> compressionStreamCreator,
|
||||
ref Stream archiveStream)
|
||||
{
|
||||
CrcStream crcStream = new CrcStream(fileStream);
|
||||
|
||||
ConcatStream concatStream = new ConcatStream(
|
||||
delegate(ConcatStream s)
|
||||
{
|
||||
this.currentArchiveBytesProcessed = s.Source.Position;
|
||||
streamContext.CloseArchiveReadStream(
|
||||
this.currentArchiveNumber,
|
||||
String.Empty,
|
||||
s.Source);
|
||||
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.FinishArchive);
|
||||
this.currentArchiveNumber += 2;
|
||||
this.currentArchiveName = null;
|
||||
this.currentArchiveBytesProcessed = 0;
|
||||
this.currentArchiveTotalBytes = 0;
|
||||
|
||||
s.Source = this.OpenArchive(streamContext, this.currentArchiveNumber);
|
||||
|
||||
FileStream archiveFileStream = s.Source as FileStream;
|
||||
this.currentArchiveName = (archiveFileStream != null ?
|
||||
Path.GetFileName(archiveFileStream.Name) : null);
|
||||
|
||||
this.currentArchiveTotalBytes = s.Source.Length;
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.StartArchive);
|
||||
this.currentArchiveNumber++;
|
||||
});
|
||||
|
||||
concatStream.Source = archiveStream;
|
||||
concatStream.SetLength(compressedSize);
|
||||
|
||||
Stream decompressionStream = compressionStreamCreator(concatStream);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] buf = new byte[4096];
|
||||
long bytesRemaining = uncompressedSize;
|
||||
int counter = 0;
|
||||
while (bytesRemaining > 0)
|
||||
{
|
||||
int count = (int)Math.Min(buf.Length, bytesRemaining);
|
||||
count = decompressionStream.Read(buf, 0, count);
|
||||
crcStream.Write(buf, 0, count);
|
||||
bytesRemaining -= count;
|
||||
|
||||
this.fileBytesProcessed += count;
|
||||
this.currentFileBytesProcessed += count;
|
||||
this.currentArchiveBytesProcessed = concatStream.Source.Position;
|
||||
|
||||
if (++counter % 16 == 0) // Report every 64K
|
||||
{
|
||||
this.currentArchiveNumber--;
|
||||
this.OnProgress(ArchiveProgressType.PartialFile);
|
||||
this.currentArchiveNumber++;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
archiveStream = concatStream.Source;
|
||||
}
|
||||
|
||||
crcStream.Flush();
|
||||
|
||||
if (crcStream.Crc != crc)
|
||||
{
|
||||
throw new ZipException("CRC check failed for file: " + fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
[assembly: AssemblyTitle("Microsoft.PackageManagement.Archivers")]
|
||||
[assembly: AssemblyDescription("Archiver providers for Zip and Cab files")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Microsoft.PackageManagement.Archivers")]
|
||||
[assembly: AssemblyCopyright("Copyright Microsoft © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
||||
[assembly: Guid("78cd1d7f-c276-4dbf-bb7a-a323dd6d96ed")]
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Archivers.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Compression;
|
||||
using Compression.Zip;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
|
||||
public class ZipArchiver {
|
||||
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> {
|
||||
{Constants.Features.SupportedExtensions, new[] {"zip"}},
|
||||
{Constants.Features.MagicSignatures, new[] {Constants.Signatures.Zip}},
|
||||
};
|
||||
|
||||
public void InitializeProvider(Request request) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of strings to the client advertizing features this provider supports.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetFeatures(Request request) {
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
if( request == null ) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Calling '{0}::GetFeatures' ", ArchiverName );
|
||||
|
||||
foreach (var feature in _features) {
|
||||
request.Yield(feature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the Provider.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ArchiverName {
|
||||
get { return "zipfile"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the version of the Provider.
|
||||
/// </summary>
|
||||
/// <returns>The version of this provider </returns>
|
||||
public string ProviderVersion {
|
||||
get {
|
||||
return "1.0.0.0";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is just here as to give us some possibility of knowing when an unexception happens...
|
||||
/// At the very least, we'll write it to the system debug channel, so a developer can find it if they are looking for it.
|
||||
/// </summary>
|
||||
public void OnUnhandledException(string methodName, Exception exception) {
|
||||
if( exception != null ) {
|
||||
Debug.WriteLine("Unexpected Exception thrown in '{0}::{1}' -- {2}\\{3}\r\n{4}", ArchiverName, methodName, exception.GetType().Name, exception.Message, exception.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> UnpackArchive(string localFilename, string destinationFolder, Request request) {
|
||||
return _UnpackArchive(localFilename, destinationFolder, request);
|
||||
}
|
||||
|
||||
private IEnumerable<string> _UnpackArchive(string localFilename, string destinationFolder, Request request) {
|
||||
var info = new ZipInfo(localFilename);
|
||||
var files = info.GetFiles();
|
||||
var percent = 0;
|
||||
var index = 0;
|
||||
var processed = new List<string>();
|
||||
|
||||
// request.Debug("Unpacking {0} {1}", localFilename, destinationFolder);
|
||||
var pid = request.StartProgress(0, "Unpacking Archive '{0}' ", Path.GetFileName(localFilename));
|
||||
try {
|
||||
info.Unpack(destinationFolder, (sender, args) => {
|
||||
if (args.ProgressType == ArchiveProgressType.FinishFile) {
|
||||
processed.Add(Path.Combine(destinationFolder, args.CurrentFileName));
|
||||
index++;
|
||||
var complete = (index*100)/files.Count;
|
||||
if (complete != percent) {
|
||||
percent = complete;
|
||||
request.Progress(pid, percent, "Unpacked {0}", args.CurrentFileName);
|
||||
}
|
||||
/*
|
||||
* Does not currently support cancellation .
|
||||
* Todo: add cancellation support to DTF compression classes.
|
||||
* */
|
||||
if (request.IsCanceled) {
|
||||
throw new OperationCanceledException("cancelling");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (OperationCanceledException) {
|
||||
// no worries.
|
||||
}
|
||||
|
||||
// request.Debug("DONE Unpacking {0} {1}", localFilename, destinationFolder);
|
||||
request.CompleteProgress(pid, true);
|
||||
|
||||
// return the list of files to the parent.
|
||||
return processed.ToArray();
|
||||
}
|
||||
|
||||
public bool IsSupportedFile(string localFilename) {
|
||||
try {
|
||||
var ze = new ZipEngine();
|
||||
using (var zipFile = File.OpenRead(localFilename)) {
|
||||
return ze.IsArchive(zipFile);
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabEngine.cs": "Compression/Cab/CabEngine.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabException.cs": "Compression/Cab/CabException.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabFileInfo.cs": "Compression/Cab/CabFileInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabInfo.cs": "Compression/Cab/CabInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabPacker.cs": "Compression/Cab/CabPacker.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabUnpacker.cs": "Compression/Cab/CabUnpacker.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/CabWorker.cs": "Compression/Cab/CabWorker.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/HandleManager.cs": "Compression/Cab/HandleManager.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Cab/NativeMethods.cs": "Compression/Cab/NativeMethods.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ConcatStream.cs": "Compression/Zip/ConcatStream.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/CrcStream.cs": "Compression/Zip/CrcStream.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipCompressionMethod.cs": "Compression/Zip/ZipCompressionMethod.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipEngine.cs": "Compression/Zip/ZipEngine.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipException.cs": "Compression/Zip/ZipException.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipFileInfo.cs": "Compression/Zip/ZipFileInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipFormat.cs": "Compression/Zip/ZipFormat.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipInfo.cs": "Compression/Zip/ZipInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipPacker.cs": "Compression/Zip/ZipPacker.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/Zip/ZipUnpacker.cs": "Compression/Zip/ZipUnpacker.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveException.cs": "Compression/ArchiveException.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveFileInfo.cs": "Compression/ArchiveFileInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveFileStreamContext.cs": "Compression/ArchiveFileStreamContext.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveInfo.cs": "Compression/ArchiveInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveProgressEventArgs.cs": "Compression/ArchiveProgressEventArgs.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/ArchiveProgressType.cs": "Compression/ArchiveProgressType.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/BasicUnpackStreamContext.cs": "Compression/BasicUnpackStreamContext.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/CargoStream.cs": "Compression/CargoStream.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/CompressionEngine.cs": "Compression/CompressionEngine.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/CompressionLevel.cs": "Compression/CompressionLevel.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/DuplicateStream.cs": "Compression/DuplicateStream.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/IPackStreamContext.cs": "Compression/IPackStreamContext.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/IUnpackStreamContext.cs": "Compression/IUnpackStreamContext.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/OffsetStream.cs": "Compression/OffsetStream.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Compression/SafeNativeMethods.cs": "Compression/SafeNativeMethods.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/Resources/Messages.resx": "resources/Microsoft.PackageManagement.Archivers.Resources.Messages.resx",
|
||||
"monad/src/oneget/providers/inbox/archivers/provider.manifest": "provider.manifest",
|
||||
"monad/src/oneget/providers/inbox/archivers/Properties/AssemblyInfo.cs": "Properties/AssemblyInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/CabArchiver.cs": "CabArchiver.cs",
|
||||
"monad/src/oneget/providers/inbox/archivers/ZipArchiver.cs": "ZipArchiver.cs"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "Microsoft.PackageManagement.ArchiverProviders",
|
||||
"version": "1.0.0-*",
|
||||
"authors": ["quoct"],
|
||||
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
|
||||
"configurations": {
|
||||
"Linux": {
|
||||
"buildOptions": {
|
||||
"define": ["LINUX"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"System.Management.Automation": "1.0.0-*",
|
||||
"Microsoft.PackageManagement": "1.0.0-*"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": "",
|
||||
"System.Numerics": "",
|
||||
"System.Data": "",
|
||||
"System.DirectoryServices": "",
|
||||
"System.Security": "",
|
||||
"System.Transactions": "",
|
||||
"System.Runtime.Serialization": "",
|
||||
"System.Management": "",
|
||||
"System.Configuration": "",
|
||||
"System.Configuration.Install": "",
|
||||
"System.Net": "",
|
||||
"System.Net.Http": "",
|
||||
"System.IO.Compression": "",
|
||||
"System.IO.Compression.FileSystem": ""
|
||||
}
|
||||
},
|
||||
|
||||
"netstandard1.5": {
|
||||
"imports": ["dnxcore50"],
|
||||
"buildOptions": {
|
||||
"define": ["CORECLR"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<SoftwareIdentity
|
||||
name="Zip and Cab Archiver Providers for PackageManagement"
|
||||
version="1.0.0.0" versionScheme="multipartnumeric"
|
||||
media="(windows) and (PackageManagement) and (min-PackageManagement.api:2)"
|
||||
xmlns="http://standards.iso.org/iso/19770/-2/2015/schema.xsd">
|
||||
|
||||
</SoftwareIdentity>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 1.3
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">1.3</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1">this is my long string</data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
[base64 mime encoded serialized .NET Framework object]
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
[base64 mime encoded string representing a byte array form of the .NET Framework object]
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>1.3</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,712 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Threading.Tasks;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Api;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using PackageManagement.Packaging;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
using PackageManagement.Internal.Utility.Plugin;
|
||||
using PackageManagement.Internal.Utility.Versions;
|
||||
using Directory = System.IO.Directory;
|
||||
using ErrorCategory = PackageManagement.Internal.ErrorCategory;
|
||||
using File = System.IO.File;
|
||||
using System.IO.Compression;
|
||||
|
||||
public class BootstrapProvider {
|
||||
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> {
|
||||
// {Constants.Features.SupportedSchemes, new[] {"http", "https", "file"}},
|
||||
// {Constants.Features.SupportedExtensions, new[] {"exe", "msi"}},
|
||||
{Constants.Features.MagicSignatures, Constants.Empty},
|
||||
{Constants.Features.AutomationOnly, Constants.Empty}
|
||||
};
|
||||
|
||||
private const WildcardOptions WildcardOptions = System.Management.Automation.WildcardOptions.CultureInvariant | System.Management.Automation.WildcardOptions.IgnoreCase;
|
||||
|
||||
private static IEqualityComparer<Package> PackageEqualityComparer = new PackageManagement.Internal.Utility.Extensions.EqualityComparer<Package>(
|
||||
(x, y) => x.Name.EqualsIgnoreCase(y.Name) && x.Version.EqualsIgnoreCase(y.Version), (x) => (x.Name + x.Version).GetHashCode());
|
||||
|
||||
private PackageManagementService PackageManagementService {
|
||||
get {
|
||||
return PackageManager.Instance as PackageManagementService;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the Provider.
|
||||
/// </summary>
|
||||
/// <required />
|
||||
/// <returns>the name of the package provider</returns>
|
||||
public string PackageProviderName {
|
||||
get {
|
||||
return "Bootstrap";
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Plugin requirement.")]
|
||||
public void InitializeProvider(BootstrapRequest request) {
|
||||
// we should go find out what's available once here just to make sure that
|
||||
// we have a list
|
||||
try {
|
||||
request.Debug("Initialize Bootstrapper");
|
||||
Task.Factory.StartNew(() => {
|
||||
// we can do this asynchronously, it'll cut down on any startup delay when the network is slow or unavailable.
|
||||
try {
|
||||
PackageManagementService.BootstrappableProviderNames = request.Providers.Select(provider => provider.Name).ToArray();
|
||||
|
||||
} catch (Exception e) {
|
||||
// if we have a serious problem, it just means we can't bootstrap those providers anyway.
|
||||
// in the event of a catastrophic failure, request isn't going to be valid anymore (and hence the user won't see it)
|
||||
// but we can send the error to the system debug output.
|
||||
e.Dump();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.Dump();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Plugin requirement.")]
|
||||
public void GetFeatures(BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Calling 'Bootstrap::GetFeatures'");
|
||||
foreach (var feature in _features) {
|
||||
request.Yield(feature);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Plugin requirement.")]
|
||||
public void GetDynamicOptions(string category, BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Calling 'Bootstrap::GetDynamicOptions ({0})'", category);
|
||||
|
||||
switch ((category ?? string.Empty).ToLowerInvariant()) {
|
||||
case "package":
|
||||
break;
|
||||
|
||||
case "source":
|
||||
break;
|
||||
|
||||
case "install":
|
||||
request.YieldDynamicOption("DestinationPath", "Folder", false);
|
||||
request.YieldDynamicOption("Scope", "String", false, new[] { "CurrentUser", "AllUsers" });
|
||||
request.YieldDynamicOption("DisplayLongSourceName", "Switch", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResolvePackageSources(BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Calling ResolvePackageSources");
|
||||
|
||||
try {
|
||||
if (request.LocalSource.Any()) {
|
||||
foreach (var source in request.LocalSource) {
|
||||
request.YieldPackageSource(source, source, false, true, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var source in request._urls) {
|
||||
|
||||
request.YieldPackageSource(source.AbsoluteUri, source.AbsoluteUri, false, true, true);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.Dump();
|
||||
}
|
||||
}
|
||||
|
||||
public void FindPackage(string name, string requiredVersion, string minimumVersion, string maximumVersion, int id, BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Verbose(Resources.Messages.FindingPackage, string.Format(CultureInfo.CurrentCulture, "{0}::FindPackage' '{1}','{2}','{3}','{4}'", PackageProviderName, name, requiredVersion, minimumVersion, maximumVersion));
|
||||
|
||||
if (name != null && name.EqualsIgnoreCase("PackageManagement")) {
|
||||
// they are looking for PackageManagement itself.
|
||||
// future todo: let PackageManagement update itself.
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.LocalSource.Any()) {
|
||||
// find a provider from given path
|
||||
request.FindProviderFromFile(name, requiredVersion, minimumVersion, maximumVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// are they are looking for a specific provider?
|
||||
if (string.IsNullOrWhiteSpace(name) || WildcardPattern.ContainsWildcardCharacters(name)) {
|
||||
|
||||
|
||||
// no, return all providers that match the range.
|
||||
var wildcardPattern = new WildcardPattern(name, WildcardOptions);
|
||||
|
||||
if (request.GetOptionValue("AllVersions").IsTrue()) {
|
||||
// Feed.Query() can return an empty provider, so here we need to execlude it by checking p.Name !=null or empty.
|
||||
foreach (var p in request.Providers.Distinct(PackageEqualityComparer).Where(p => !string.IsNullOrWhiteSpace(p.Name) && (string.IsNullOrWhiteSpace(name) || wildcardPattern.IsMatch(p.Name)))) {
|
||||
FindPackage(p.Name, null, "0.0", null, 0, request);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.Providers.Distinct(PackageEqualityComparer).Where(p => string.IsNullOrWhiteSpace(name) || wildcardPattern.IsMatch(p.Name)).Any(p => !request.YieldFromSwidtag(p, requiredVersion, minimumVersion, maximumVersion, name))) {
|
||||
// if there is a problem, exit.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// return just the one they asked for.
|
||||
|
||||
// asked for a specific version?
|
||||
if (!string.IsNullOrWhiteSpace(requiredVersion)) {
|
||||
request.YieldFromSwidtag(request.GetProvider(name, requiredVersion), name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.GetOptionValue("AllVersions").IsTrue()) {
|
||||
if (request.GetProviderAll(name, minimumVersion, maximumVersion).Distinct(PackageEqualityComparer).Any(provider => !request.YieldFromSwidtag(provider, name))) {
|
||||
// if there is a problem, exit.
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// asked for a version range?
|
||||
if (!string.IsNullOrWhiteSpace(minimumVersion) || !string.IsNullOrEmpty(maximumVersion)) {
|
||||
if (request.GetProvider(name, minimumVersion, maximumVersion).Distinct(PackageEqualityComparer).Any(provider => !request.YieldFromSwidtag(provider, name))) {
|
||||
// if there is a problem, exit.
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// just return by name
|
||||
request.YieldFromSwidtag(request.GetProvider(name), name);
|
||||
}
|
||||
|
||||
// return any matches in the name
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packages that are installed
|
||||
/// </summary>
|
||||
/// <param name="name">the package name to match. Empty or null means match everything</param>
|
||||
/// <param name="requiredVersion">
|
||||
/// the specific version asked for. If this parameter is specified (ie, not null or empty
|
||||
/// string) then the minimum and maximum values are ignored
|
||||
/// </param>
|
||||
/// <param name="minimumVersion">
|
||||
/// the minimum version of packages to return . If the <code>requiredVersion</code> parameter
|
||||
/// is specified (ie, not null or empty string) this should be ignored
|
||||
/// </param>
|
||||
/// <param name="maximumVersion">
|
||||
/// the maximum version of packages to return . If the <code>requiredVersion</code> parameter
|
||||
/// is specified (ie, not null or empty string) this should be ignored
|
||||
/// </param>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Plugin requirement.")]
|
||||
public void GetInstalledPackages(string name, string requiredVersion, string minimumVersion, string maximumVersion, BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Calling '{0}::GetInstalledPackages' '{1}','{2}','{3}','{4}'", PackageProviderName, name, requiredVersion, minimumVersion, maximumVersion);
|
||||
|
||||
//search under the providerAssembies folder for the installed providers
|
||||
var providers = PackageManagementService.AllProvidersFromProviderAssembliesLocation(request).Select(providerFileAssembly => {
|
||||
|
||||
//get the provider's name\version
|
||||
var versionFolder = Path.GetDirectoryName(providerFileAssembly);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(versionFolder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Version ver;
|
||||
if (!Version.TryParse(Path.GetFileName(versionFolder), out ver)) {
|
||||
//this will cover whether the providerFileAssembly is at top level as well as a bad version folder
|
||||
//skip if the provider is at the top level as they are imported already via LoadProviders() during the initialization.
|
||||
//the provider will be handled PackageManagementService.DynamicProviders below.
|
||||
return null;
|
||||
}
|
||||
|
||||
var providerNameFolder = Path.GetDirectoryName(versionFolder);
|
||||
if (!string.IsNullOrWhiteSpace(providerNameFolder)) {
|
||||
var providerName = Path.GetFileName(providerNameFolder);
|
||||
if (!string.IsNullOrWhiteSpace(providerName)) {
|
||||
return new {
|
||||
Name = providerName,
|
||||
Version = (FourPartVersion)ver,
|
||||
ProviderPath = providerFileAssembly
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}).WhereNotNull();
|
||||
|
||||
// return all the dynamic package providers as packages
|
||||
providers = providers.Concat(PackageManagementService.DynamicProviders.Select(each => new {
|
||||
Name = each.ProviderName,
|
||||
each.Version,
|
||||
each.ProviderPath
|
||||
})).Distinct();
|
||||
|
||||
var pp = request.LocalSource.Any() ? providers.Select(each => request.GetProviderFromFile(each.ProviderPath, false, true)).WhereNotNull() :
|
||||
providers.Select(each => request.GetProvider(each.Name, each.Version)).WhereNotNull();
|
||||
|
||||
foreach (var p in pp) {
|
||||
request.YieldFromSwidtag(p, requiredVersion, minimumVersion, maximumVersion, name);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Plugin requirement.")]
|
||||
public void DownloadPackage(string fastPath, string location, BootstrapRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
request.Debug("Calling 'Bootstrap::DownloadPackage'");
|
||||
}
|
||||
|
||||
private bool InstallProviderFromInstaller(Package provider, Link link, string fastPath, BootstrapRequest request) {
|
||||
switch (link.MediaType) {
|
||||
case Iso19770_2.MediaType.MsiPackage:
|
||||
case Iso19770_2.MediaType.MsuPackage:
|
||||
return InstallPackageFile(provider, fastPath, request);
|
||||
|
||||
case Iso19770_2.MediaType.PackageReference:
|
||||
// let the core figure out how to install this package
|
||||
var packages = PackageManagementService.FindPackageByCanonicalId(link.HRef.AbsoluteUri, request).ToArray();
|
||||
switch (packages.Length) {
|
||||
case 0:
|
||||
request.Warning("Unable to resolve package reference '{0}'", link.HRef);
|
||||
return false;
|
||||
|
||||
case 1:
|
||||
return InstallPackageReference(provider, fastPath, request, packages);
|
||||
|
||||
default:
|
||||
request.Warning("Package Reference '{0}' resolves to {1} packages.", link.HRef, packages.Length);
|
||||
return false;
|
||||
}
|
||||
|
||||
case Iso19770_2.MediaType.NuGetPackage:
|
||||
return InstallNugetPackage(provider, link, fastPath, request);
|
||||
|
||||
default:
|
||||
request.Warning("Provider '{0}' with link '{1}' has unknown media type '{2}'.", provider.Name, link.HRef, link.MediaType);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InstallNugetPackage(Package provider, Link link, string fastPath, BootstrapRequest request)
|
||||
{
|
||||
// download the nuget package
|
||||
string downloadedNupkg = request.DownloadAndValidateFile(provider._swidtag);
|
||||
|
||||
if (downloadedNupkg != null)
|
||||
{
|
||||
// extracted folder
|
||||
string extractedFolder = String.Concat(FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory());
|
||||
|
||||
try
|
||||
{
|
||||
//unzip the file
|
||||
ZipFile.ExtractToDirectory(downloadedNupkg, extractedFolder);
|
||||
|
||||
if (Directory.Exists(extractedFolder))
|
||||
{
|
||||
string versionFolder = Path.Combine(request.DestinationPath(request), provider.Name, provider.Version);
|
||||
// tool folder is where we find things like nuget.exe
|
||||
string toolFolder = Path.Combine(extractedFolder, "tools");
|
||||
string libFolder = Path.Combine(extractedFolder, "lib");
|
||||
|
||||
// create the directory version folder if not exist
|
||||
if (!Directory.Exists(versionFolder))
|
||||
{
|
||||
Directory.CreateDirectory(versionFolder);
|
||||
}
|
||||
|
||||
// copy the tools directory
|
||||
if (Directory.Exists(toolFolder))
|
||||
{
|
||||
string destinationToolFolder = Path.Combine(versionFolder, "tools");
|
||||
|
||||
if (!Directory.Exists(destinationToolFolder))
|
||||
{
|
||||
Directory.CreateDirectory(destinationToolFolder);
|
||||
}
|
||||
|
||||
foreach (string child in Directory.EnumerateFiles(toolFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
// try copy and overwrite
|
||||
File.Copy(child, Path.Combine(destinationToolFolder, Path.GetFileName(child)), true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
request.Debug(e.StackTrace);
|
||||
if (!(e is UnauthorizedAccessException || e is IOException))
|
||||
{
|
||||
// something wrong, delete the version folder
|
||||
versionFolder.TryHardToDelete();
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise this means the file is just being used. so just moves on to copy other files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// copy files from lib
|
||||
if (Directory.Exists(libFolder))
|
||||
{
|
||||
// check that the lib folder has at most 1 dll
|
||||
if (Directory.EnumerateFiles(libFolder).Count(file => String.Equals(Path.GetExtension(file), ".dll", StringComparison.OrdinalIgnoreCase)) > 1)
|
||||
{
|
||||
request.Warning(String.Format(CultureInfo.CurrentCulture, Resources.Messages.MoreThanOneDllExists, provider.Name));
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string child in Directory.EnumerateFiles(libFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Copy(child, Path.Combine(versionFolder, Path.GetFileName(child)), true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
request.Debug(e.StackTrace);
|
||||
if (!(e is UnauthorizedAccessException || e is IOException))
|
||||
{
|
||||
// something wrong, delete the version folder
|
||||
versionFolder.TryHardToDelete();
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise this means the file is just being used. so just moves on to copy other files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// target file name is the assembly provider
|
||||
string targetFile = Path.Combine(versionFolder, Path.GetFileName(link.Attributes[Iso19770_2.Discovery.TargetFilename]));
|
||||
|
||||
if (File.Exists(targetFile))
|
||||
{
|
||||
request.Verbose(Resources.Messages.InstalledPackage, provider.Name, targetFile);
|
||||
request.YieldFromSwidtag(provider, fastPath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
downloadedNupkg.TryHardToDelete();
|
||||
extractedFolder.TryHardToDelete();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool InstallPackageFile(Package provider, string fastPath, BootstrapRequest request) {
|
||||
// we can download and verify this package and get the core to install it.
|
||||
var file = request.DownloadAndValidateFile(provider._swidtag);
|
||||
if (file != null) {
|
||||
// we have a valid file.
|
||||
// run the installer
|
||||
if (request.ProviderServices.Install(file, "", request)) {
|
||||
// it installed ok!
|
||||
request.YieldFromSwidtag(provider, fastPath);
|
||||
PackageManagementService.LoadProviders(request.As<IRequest>());
|
||||
return true;
|
||||
}
|
||||
request.Warning(Constants.Messages.FailedProviderBootstrap, fastPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool InstallPackageReference(Package provider, string fastPath, BootstrapRequest request, SoftwareIdentity[] packages) {
|
||||
IHostApi installRequest = request;
|
||||
if (packages[0].Provider.Name.EqualsIgnoreCase("PowerShellGet") && !request.ProviderServices.IsElevated) {
|
||||
// if we're not elevated, we want powershellget to install to the user scope
|
||||
|
||||
installRequest = new object[] {
|
||||
new {
|
||||
GetOptionKeys = new Func<IEnumerable<string>>(() => request.OptionKeys.ConcatSingleItem("Scope")),
|
||||
GetOptionValues = new Func<string, IEnumerable<string>>((key) => {
|
||||
if (key != null && key.EqualsIgnoreCase("Scope")) {
|
||||
return "CurrentUser".SingleItemAsEnumerable();
|
||||
}
|
||||
return request.GetOptionValues(key);
|
||||
})
|
||||
}
|
||||
, installRequest
|
||||
}.As<IHostApi>();
|
||||
}
|
||||
|
||||
var installing = packages[0].Provider.InstallPackage(packages[0], installRequest);
|
||||
|
||||
SoftwareIdentity lastPackage = null;
|
||||
|
||||
foreach (var i in installing) {
|
||||
lastPackage = i;
|
||||
// should we echo each package back as it comes back?
|
||||
request.YieldSoftwareIdentity(i.FastPackageReference, i.Name, i.Version, i.VersionScheme, i.Summary, i.Source, i.SearchKey, i.FullPath, i.PackageFilename);
|
||||
|
||||
if (request.IsCanceled) {
|
||||
installing.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.IsCanceled && lastPackage != null) {
|
||||
if (provider.Name.EqualsIgnoreCase("PowerShellGet")) {
|
||||
// special case. PSModules we can just ask the PowerShell provider to pick it up
|
||||
// rather than try to scan for it.
|
||||
PackageManagementService.TryLoadProviderViaMetaProvider("PowerShell", lastPackage.FullPath, request);
|
||||
request.YieldFromSwidtag(provider, fastPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
// looks like it installed ok.
|
||||
request.YieldFromSwidtag(provider, fastPath);
|
||||
|
||||
// rescan providers
|
||||
PackageManagementService.LoadProviders(request.As<IRequest>());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool InstallAssemblyProvider(Package provider, Link link, string fastPath, BootstrapRequest request, bool deleteFile=true) {
|
||||
request.Verbose(Resources.Messages.InstallingPackage, fastPath);
|
||||
|
||||
if (!Directory.Exists(request.DestinationPath(request))) {
|
||||
request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.DestinationPathNotSet);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
string targetFilename = fastPath;
|
||||
string file = fastPath;
|
||||
|
||||
//source can be from install-packageprovider or can be from the pipeline
|
||||
if (!request.LocalSource.Any() && !fastPath.IsFile() && link != null) {
|
||||
|
||||
targetFilename = link.Attributes[Iso19770_2.Discovery.TargetFilename];
|
||||
|
||||
// download the file
|
||||
file = request.DownloadAndValidateFile(provider._swidtag);
|
||||
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(targetFilename)) {
|
||||
request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.InvalidFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
targetFilename = Path.GetFileName(targetFilename);
|
||||
if (string.IsNullOrWhiteSpace(provider.Version)) {
|
||||
request.Error(ErrorCategory.InvalidOperation, fastPath, Resources.Messages.MissingVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
//the provider is installing to like this folder: \WindowsPowerShell\Modules\PackageManagement\ProviderAssemblies\nuget\2.8.5.127
|
||||
//... providername\version\.dll
|
||||
var versionFolder = Path.Combine(request.DestinationPath(request), provider.Name, provider.Version);
|
||||
|
||||
if (!Directory.Exists(versionFolder)) {
|
||||
//we create it
|
||||
Directory.CreateDirectory(versionFolder);
|
||||
}
|
||||
|
||||
var targetFile = Path.Combine(versionFolder, targetFilename);
|
||||
|
||||
if (file != null) {
|
||||
try
|
||||
{
|
||||
// looks good! let's keep it
|
||||
if (File.Exists(targetFile)) {
|
||||
request.Debug("Removing old file '{0}'", targetFile);
|
||||
targetFile.TryHardToDelete();
|
||||
}
|
||||
|
||||
// is that file still there?
|
||||
if (File.Exists(targetFile)) {
|
||||
request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.UnableToRemoveFile, targetFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
request.Debug("Copying file '{0}' to '{1}'", file, targetFile);
|
||||
try {
|
||||
File.Copy(file, targetFile);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
request.Debug(ex.StackTrace);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (File.Exists(targetFile)) {
|
||||
request.Verbose(Resources.Messages.InstalledPackage, provider.Name, targetFile);
|
||||
request.YieldFromSwidtag(provider, fastPath);
|
||||
|
||||
//Load the provider. This is needed when a provider has dependencies. For example, if Nuget provider has a dependent foobar
|
||||
// provider and when 'Install-PackageProvider -name NuGet', we want both NuGet and Foobar provider gets loaded.
|
||||
PackageManagementService.LoadProviderAssembly(request, targetFile, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (deleteFile) {
|
||||
file.TryHardToDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InstallPackageFromFile(string fastPath, BootstrapRequest request)
|
||||
{
|
||||
var filePath = new Uri(fastPath).LocalPath;
|
||||
|
||||
var pkg = request.GetProviderFromFile(filePath, true, false);
|
||||
|
||||
if (pkg != null) {
|
||||
InstallAssemblyProvider(pkg, null, filePath, request, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void InstallPackage(string fastPath, BootstrapRequest request)
|
||||
{
|
||||
InstallPackage(fastPath, request, false);
|
||||
}
|
||||
|
||||
internal void InstallPackage(string fastPath, BootstrapRequest request, bool errorContinue) {
|
||||
if (fastPath == null) {
|
||||
throw new ArgumentNullException("fastPath");
|
||||
}
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
// ensure that mandatory parameters are present.
|
||||
request.Debug("Calling 'Bootstrap::InstallPackage'");
|
||||
var triedAndFailed = false;
|
||||
|
||||
//source can be from install - packageprovider or can be from the pipeline
|
||||
if ((request.LocalSource.Any() || fastPath.IsFile()))
|
||||
{
|
||||
InstallPackageFromFile(fastPath, request);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify the package integrity (ie, check if it's digitally signed before installing)
|
||||
|
||||
var provider = request.GetProvider(new Uri(fastPath));
|
||||
if (provider == null || !provider.IsValid) {
|
||||
var result = errorContinue ? request.Warning(Constants.Messages.UnableToResolvePackage, fastPath) : request.Error(ErrorCategory.InvalidData, fastPath, Constants.Messages.UnableToResolvePackage, fastPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// first install the dependencies if any
|
||||
var dependencyLinks = provider._swidtag.Links.Where(link => link.Relationship == Iso19770_2.Relationship.Requires).GroupBy(link => link.Artifact);
|
||||
foreach (var depLinks in dependencyLinks) {
|
||||
foreach (var item in depLinks) {
|
||||
Package packages = null;
|
||||
if (string.IsNullOrWhiteSpace(item.Attributes[Iso19770_2.Discovery.Name])) {
|
||||
//select the packages that marked as "latest"
|
||||
packages = (new Feed(request, new[] {item.HRef})).Query().FirstOrDefault();
|
||||
} else {
|
||||
if (string.IsNullOrWhiteSpace(item.Attributes[Iso19770_2.Discovery.Version])) {
|
||||
//select the packages that marked as "latest" and matches the name specified
|
||||
packages = (new Feed(request, new[] { item.HRef })).Query().FirstOrDefault(p => p.Name.EqualsIgnoreCase(item.Attributes[Iso19770_2.Discovery.Name]));
|
||||
} else {
|
||||
//select the packages that matches version and name
|
||||
packages = (new Feed(request, new[] { item.HRef })).Query(item.Attributes[Iso19770_2.Discovery.Name], item.Attributes[Iso19770_2.Discovery.Version]).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
if (packages == null) {
|
||||
// no package found
|
||||
request.Warning(Resources.Messages.NoDependencyPackageFound, item.HRef);
|
||||
continue;
|
||||
}
|
||||
// try to install dependent providers. If fails, continue
|
||||
InstallPackage(packages.Location.AbsoluteUri, request, errorContinue: true);
|
||||
}
|
||||
}
|
||||
|
||||
// group the links along 'artifact' lines
|
||||
var artifacts = provider._swidtag.Links.Where(link => link.Relationship == Iso19770_2.Relationship.InstallationMedia).GroupBy(link => link.Artifact);
|
||||
|
||||
|
||||
// try one artifact set at a time.
|
||||
foreach (var artifact in artifacts) {
|
||||
// first time we succeed, we're good to go.
|
||||
foreach (var link in artifact) {
|
||||
switch (link.Attributes[Iso19770_2.Discovery.Type]) {
|
||||
case "assembly":
|
||||
if (InstallAssemblyProvider(provider, link, fastPath, request)) {
|
||||
return;
|
||||
}
|
||||
triedAndFailed = true;
|
||||
continue;
|
||||
|
||||
default:
|
||||
if (InstallProviderFromInstaller(provider, link, fastPath, request)) {
|
||||
return;
|
||||
}
|
||||
triedAndFailed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (triedAndFailed) {
|
||||
// we tried installing something and it didn't go well.
|
||||
var result = errorContinue ? request.Warning(Constants.Messages.FailedProviderBootstrap, fastPath) : request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.FailedProviderBootstrap, fastPath);
|
||||
} else {
|
||||
// we didn't even find a link to bootstrap.
|
||||
var result = errorContinue ? request.Warning(Resources.Messages.MissingInstallationmedia, fastPath) : request.Error(ErrorCategory.InvalidOperation, fastPath, Resources.Messages.MissingInstallationmedia, fastPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Management.Automation;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using PackageManagement.Internal.Utility.Platform;
|
||||
using PackageManagement.Internal.Utility.Collections;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
using ErrorCategory = PackageManagement.Internal.ErrorCategory;
|
||||
|
||||
public abstract class BootstrapRequest : Request {
|
||||
internal Uri[] _urls
|
||||
{
|
||||
get
|
||||
{
|
||||
string testfeed = Environment.GetEnvironmentVariable("BootstrapProviderTestfeedUrl");
|
||||
|
||||
// if testfeed exists, use that
|
||||
if (!string.IsNullOrWhiteSpace(testfeed))
|
||||
{
|
||||
Uri result = null;
|
||||
|
||||
// test whether the uri is valid
|
||||
if (Uri.TryCreate(testfeed, UriKind.Absolute, out result))
|
||||
{
|
||||
return new Uri[] {
|
||||
result
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new Uri[] {
|
||||
#if LOCAL_DEBUG
|
||||
new Uri("https://localhost:81/providers.swidtag"),
|
||||
#endif
|
||||
#if CORECLR
|
||||
new Uri("https://go.microsoft.com/fwlink/?LinkID=627340&clcid=0x409"),
|
||||
// starting in 2015/05 builds, we bootstrap from here:
|
||||
#else
|
||||
new Uri("https://go.microsoft.com/fwlink/?LinkID=627338&clcid=0x409"),
|
||||
#endif
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Feed> _feeds;
|
||||
private IEnumerable<string> _fileSource = null;
|
||||
|
||||
private IEnumerable<Feed> Feeds {
|
||||
get {
|
||||
if (_feeds == null) {
|
||||
if (LocalSource.Any()) {
|
||||
Verbose(Resources.Messages.UseLocalSource, LocalSource.FirstOrDefault());
|
||||
return Enumerable.Empty<Feed>();
|
||||
}
|
||||
|
||||
if (!System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable()) {
|
||||
Warning(Constants.Messages.NetworkNotAvailable);
|
||||
Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.ProviderBootstrapFailed));
|
||||
}
|
||||
|
||||
// right now, we only have one feed (can have many urls tho')
|
||||
// so we just return a single feed in the collection
|
||||
// but later, we can expand it to support multiple feeds.
|
||||
var feed = new Feed(this, _urls);
|
||||
if (feed.IsValid) {
|
||||
_feeds = feed.SingleItemAsEnumerable().ReEnumerable();
|
||||
} else {
|
||||
Warning(Constants.Messages.ProviderSwidtagUnavailable);
|
||||
return Enumerable.Empty<Feed>();
|
||||
}
|
||||
}
|
||||
return _feeds;
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<string> LocalSource {
|
||||
get
|
||||
{
|
||||
if (_fileSource == null)
|
||||
{
|
||||
if (Sources.IsNullOrEmpty())
|
||||
{
|
||||
_fileSource = Enumerable.Empty<string>();
|
||||
} else {
|
||||
_fileSource = Sources.Where(each => !string.IsNullOrWhiteSpace(each) && (System.IO.File.Exists(each) || System.IO.Directory.Exists(each))).WhereNotNull();
|
||||
}
|
||||
}
|
||||
return _fileSource;
|
||||
}
|
||||
}
|
||||
|
||||
internal string DestinationPath(Request request) {
|
||||
|
||||
var pms = PackageManagementService as PackageManagementService;
|
||||
|
||||
var scope = GetValue("Scope");
|
||||
if (!string.IsNullOrWhiteSpace(scope)) {
|
||||
if (scope.EqualsIgnoreCase("CurrentUser")) {
|
||||
return pms.UserAssemblyLocation;
|
||||
}
|
||||
if (AdminPrivilege.IsElevated) {
|
||||
return pms.SystemAssemblyLocation;
|
||||
} else {
|
||||
//a user specifies 'AllUsers' that requires Admin provilege. However his console gets launched by non-elevated.
|
||||
Error(ErrorCategory.InvalidOperation, ErrorCategory.InvalidOperation.ToString(),
|
||||
PackageManagement.Resources.Messages.InstallRequiresCurrentUserScopeParameterForNonAdminUser, pms.SystemAssemblyLocation, pms.UserAssemblyLocation);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var v = GetValue("DestinationPath");
|
||||
if (String.IsNullOrWhiteSpace(v)) {
|
||||
// use a well-known path.
|
||||
v = AdminPrivilege.IsElevated ? pms.SystemAssemblyLocation : pms.UserAssemblyLocation;
|
||||
if (String.IsNullOrWhiteSpace(v)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Path.GetFullPath(v);
|
||||
}
|
||||
|
||||
internal IEnumerable<Package> Providers {
|
||||
get {
|
||||
return Feeds.SelectMany(feed => feed.Query());
|
||||
}
|
||||
}
|
||||
|
||||
private string GetValue(string name) {
|
||||
// get the value from the request
|
||||
return (GetOptionValues(name) ?? Enumerable.Empty<string>()).LastOrDefault();
|
||||
}
|
||||
|
||||
internal Package GetProvider(Uri uri) {
|
||||
return new Package(this, uri.SingleItemAsEnumerable());
|
||||
}
|
||||
|
||||
internal Package GetProvider(string name) {
|
||||
return Feeds.SelectMany(feed => feed.Query(name)).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal Package GetProvider(string name, string version) {
|
||||
return Feeds.SelectMany(feed => feed.Query(name, version)).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal IEnumerable<Package> GetProviderAll(string name, string minimumversion, string maximumversion) {
|
||||
return Feeds.SelectMany(feed => feed.Query(name, minimumversion, maximumversion));
|
||||
}
|
||||
|
||||
internal IEnumerable<Package> GetProvider(string name, string minimumversion, string maximumversion) {
|
||||
return new[] {
|
||||
GetProviderAll(name, minimumversion, maximumversion)
|
||||
.OrderByDescending(each => SoftwareIdentityVersionComparer.Instance).FirstOrDefault()
|
||||
};
|
||||
}
|
||||
|
||||
internal string DownloadAndValidateFile(Swidtag swidtag) {
|
||||
var file = DownLoadFileFromLinks(swidtag.Links.Where(each => each.Relationship == Iso19770_2.Relationship.InstallationMedia));
|
||||
if (string.IsNullOrWhiteSpace(file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var payload = swidtag.Payload;
|
||||
if (payload == null) {
|
||||
//We let the providers that are already posted in the public continue to be installed.
|
||||
return file;
|
||||
} else {
|
||||
//validate the file hash
|
||||
var valid = ValidateFileHash(file, payload);
|
||||
if (!valid) {
|
||||
//if the hash does not match, delete the file in the temp folder
|
||||
file.TryHardToDelete();
|
||||
return null;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function to retry downloading a file.
|
||||
/// downloadFileFunction is the main function that is used to download the file when given a uri
|
||||
/// numberOfTry is how many times we can try to download it
|
||||
/// </summary>
|
||||
/// <param name="downloadFileFunction"></param>
|
||||
/// <param name="location"></param>
|
||||
/// <param name="numberOfTry"></param>
|
||||
/// <returns></returns>
|
||||
internal string RetryDownload(Func<Uri, string> downloadFileFunction, Uri location, uint numberOfTry = 3) {
|
||||
string file = null;
|
||||
|
||||
// if scheme is not https, write warning and ignores this link
|
||||
if (!string.Equals(location.Scheme, "https")) {
|
||||
Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.OnlyHttpsSchemeSupported, location.AbsoluteUri));
|
||||
return file;
|
||||
}
|
||||
|
||||
// try 3 times to see whether we can download this
|
||||
int remainingTry = 3;
|
||||
|
||||
// try to download the file for remainingTry times
|
||||
while (remainingTry > 0) {
|
||||
try {
|
||||
file = downloadFileFunction(location);
|
||||
} finally {
|
||||
if (file == null || !file.FileExists()) {
|
||||
// file cannot be download
|
||||
file = null;
|
||||
remainingTry -= 1;
|
||||
Verbose(string.Format(CultureInfo.CurrentCulture, Resources.Messages.RetryDownload, location.AbsoluteUri, remainingTry));
|
||||
} else {
|
||||
// file downloaded, no need to retry.
|
||||
remainingTry = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal string DownLoadFileFromLinks(IEnumerable<Link> links) {
|
||||
string file = null;
|
||||
|
||||
foreach (var link in links) {
|
||||
file = RetryDownload(
|
||||
// the download function takes in a uri link and download it
|
||||
(uri) => {
|
||||
var tmpFile = FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory();
|
||||
return ProviderServices.DownloadFile(uri, tmpFile, -1, true, this);
|
||||
},
|
||||
link.HRef);
|
||||
|
||||
// got a valid file!
|
||||
if (file != null && file.FileExists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private bool ValidateFileHash(string fileFullPath, Payload payload) {
|
||||
|
||||
Debug("BoostrapRequest::ValidateFileHash");
|
||||
/* format:
|
||||
* <Payload>
|
||||
* <File name="nuget-anycpu-2.8.5.205.exe" sha512:hash="a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a" />
|
||||
* </Payload>
|
||||
*/
|
||||
|
||||
if (payload == null || fileFullPath == null || !fileFullPath.FileExists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if ((payload.Files == null) || !payload.Files.Any()) {
|
||||
Error(ErrorCategory.InvalidData, "Payload", Constants.Messages.MissingFileTag);
|
||||
return false;
|
||||
}
|
||||
var fileTag = payload.Files.FirstOrDefault();
|
||||
|
||||
if ((fileTag.Attributes == null) || (fileTag.Attributes.Keys == null)) {
|
||||
Error(ErrorCategory.InvalidData, "Payload", Constants.Messages.MissingHashAttribute);
|
||||
return false;
|
||||
}
|
||||
|
||||
var hashtag = fileTag.Attributes.Keys.FirstOrDefault(each => each.LocalName.Equals("hash"));
|
||||
if (hashtag == null) {
|
||||
Error(ErrorCategory.InvalidData, "Payload", Constants.Messages.MissingHashAttribute);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Note we cannot use switch here because these xname like Iso19770_2.Hash.Hash512, is not compiler time constant
|
||||
string packageHash = null;
|
||||
HashAlgorithm hashAlgorithm = null;
|
||||
|
||||
if (hashtag.Equals(Iso19770_2.Hash.Hash512)) {
|
||||
hashAlgorithm = SHA512.Create();
|
||||
packageHash = fileTag.GetAttribute(Iso19770_2.Hash.Hash512);
|
||||
} else if (hashtag.Equals(Iso19770_2.Hash.Hash256)) {
|
||||
hashAlgorithm = SHA256.Create();
|
||||
packageHash = fileTag.GetAttribute(Iso19770_2.Hash.Hash256);
|
||||
} else if (hashtag.Equals(Iso19770_2.Hash.Md5)) {
|
||||
hashAlgorithm = MD5.Create();
|
||||
packageHash = fileTag.GetAttribute(Iso19770_2.Hash.Md5);
|
||||
} else {
|
||||
//hash alroghtme not supported, we support 512, 256, md5 only
|
||||
Error(ErrorCategory.InvalidData, "Payload", Constants.Messages.UnsupportedHashAlgorithm, hashtag,
|
||||
new[] {Iso19770_2.HashAlgorithm.Sha512, Iso19770_2.HashAlgorithm.Sha256, Iso19770_2.HashAlgorithm.Md5}.JoinWithComma());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(packageHash) || hashAlgorithm == null) {
|
||||
//missing hash content?
|
||||
Error(ErrorCategory.InvalidData, "Payload", Constants.Messages.MissingHashContent);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the hash
|
||||
using (FileStream stream = System.IO.File.OpenRead(fileFullPath)) {
|
||||
// compute the hash from the file
|
||||
byte[] computedHash = hashAlgorithm.ComputeHash(stream);
|
||||
|
||||
try {
|
||||
// convert the original hash we got from the payload tag
|
||||
byte[] expectedHash = Convert.FromBase64String(packageHash);
|
||||
//check if hash is equal
|
||||
if (!computedHash.SequenceEqual(expectedHash)) {
|
||||
// the file downloaded is not the same as expected. The file is modified.
|
||||
Error(ErrorCategory.SecurityError, "Payload", Constants.Messages.HashNotEqual, packageHash, Convert.ToBase64String(computedHash));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (FormatException ex) {
|
||||
Warning(ex.Message);
|
||||
Error(ErrorCategory.SecurityError, "Payload", Constants.Messages.InvalidHashFormat, packageHash);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Warning(ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool YieldFromSwidtag(Package provider, string requiredVersion, string minimumVersion, string maximumVersion, string searchKey) {
|
||||
if (provider == null) {
|
||||
// if the provider isn't there, just return.
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
if (AnyNullOrEmpty(provider.Name, provider.Version, provider.VersionScheme)) {
|
||||
Debug("Skipping yield on swid due to missing field \r\n", provider.ToString());
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(requiredVersion)) {
|
||||
if (provider.Version != requiredVersion) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
} else {
|
||||
if (!String.IsNullOrWhiteSpace(minimumVersion) && SoftwareIdentityVersionComparer.CompareVersions(provider.VersionScheme, provider.Version, minimumVersion) < 0) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(maximumVersion) && SoftwareIdentityVersionComparer.CompareVersions(provider.VersionScheme, provider.Version, maximumVersion) > 0) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
}
|
||||
return YieldFromSwidtag(provider, searchKey);
|
||||
}
|
||||
|
||||
|
||||
internal bool YieldFromSwidtag(Package pkg, string searchKey) {
|
||||
if (pkg == null) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
var provider = pkg._swidtag;
|
||||
var fastPackageReference = LocalSource.Any() ? pkg.Location.LocalPath : pkg.Location.AbsoluteUri;
|
||||
var source = pkg.Source ?? fastPackageReference;
|
||||
|
||||
var summary = pkg.Name;
|
||||
var targetFileName = pkg.Name;
|
||||
|
||||
if (!LocalSource.Any()) {
|
||||
summary = new MetadataIndexer(provider)[Iso19770_2.Attributes.Summary.LocalName].FirstOrDefault();
|
||||
targetFileName = provider.Links.Select(each => each.Attributes[Iso19770_2.Discovery.TargetFilename]).WhereNotNull().FirstOrDefault();
|
||||
}
|
||||
|
||||
if (YieldSoftwareIdentity(fastPackageReference, provider.Name, provider.Version, provider.VersionScheme, summary, source, searchKey, null, targetFileName) != null) {
|
||||
// yield all the meta/attributes
|
||||
if (provider.Meta.Any(
|
||||
m => {
|
||||
var element = AddMeta(fastPackageReference);
|
||||
var attributes = m.Attributes;
|
||||
return attributes.Keys.Any(key => {
|
||||
var nspace = key.Namespace.ToString();
|
||||
if (String.IsNullOrWhiteSpace(nspace)) {
|
||||
return AddMetadata(element, key.LocalName, attributes[key]) == null;
|
||||
}
|
||||
|
||||
return AddMetadata(element, new Uri(nspace), key.LocalName, attributes[key]) == null;
|
||||
});
|
||||
})) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
if (provider.Links.Any(link => AddLink(link.HRef, link.Relationship, link.MediaType, link.Ownership, link.Use, link.Media, link.Artifact) == null)) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
if (provider.Entities.Any(entity => AddEntity(entity.Name, entity.RegId, entity.Role, entity.Thumbprint) == null)) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
//installing a package from bootstrap site needs to prompt a user. Only auto-boostrap is not prompted.
|
||||
var pm = PackageManagementService as PackageManagementService;
|
||||
string isTrustedSource = pm.InternalPackageManagementInstallOnly ? "false" : "true";
|
||||
if (AddMetadata(fastPackageReference, "FromTrustedSource", isTrustedSource) == null) {
|
||||
return !IsCanceled;
|
||||
}
|
||||
}
|
||||
return !IsCanceled;
|
||||
}
|
||||
|
||||
private static bool AnyNullOrEmpty(params string[] args) {
|
||||
return args.Any(String.IsNullOrWhiteSpace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a package provider from a given path.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="suppressErrorsAndWarnings"></param>
|
||||
/// <param name="copyFileToTemp"></param>
|
||||
/// <returns></returns>
|
||||
internal Package GetProviderFromFile(string filePath, bool copyFileToTemp = false, bool suppressErrorsAndWarnings = false) {
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath) && !System.IO.File.Exists(filePath)) {
|
||||
Warning(Constants.Messages.FileNotFound, filePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
// support providers with .dll file extention only
|
||||
if (!Path.GetExtension(filePath).EqualsIgnoreCase(".dll")) {
|
||||
if (!suppressErrorsAndWarnings)
|
||||
{
|
||||
Warning(Resources.Messages.InvalidFileType, ".dll", filePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
string tempFile = filePath;
|
||||
IEnumerable<XElement> manifests = Enumerable.Empty<XElement>();
|
||||
if (copyFileToTemp) {
|
||||
try {
|
||||
// Manifest.LoadFrom() does not work with network share, so we need to copy the dll to temp location
|
||||
tempFile = CopyToTempLocation(filePath);
|
||||
if (string.IsNullOrWhiteSpace(tempFile) && !System.IO.File.Exists(tempFile))
|
||||
{
|
||||
Warning(Constants.Messages.FileNotFound, tempFile);
|
||||
return null;
|
||||
}
|
||||
|
||||
manifests = Manifest.LoadFrom(tempFile).ToArray();
|
||||
}
|
||||
finally {
|
||||
if (!string.IsNullOrWhiteSpace(tempFile)) {
|
||||
tempFile.TryHardToDelete();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// providers have the provider manifest embeded?
|
||||
manifests = Manifest.LoadFrom(filePath).ToArray();
|
||||
}
|
||||
|
||||
if (!manifests.Any()) {
|
||||
if (!suppressErrorsAndWarnings)
|
||||
{
|
||||
Warning(Resources.Messages.MissingProviderManifest, tempFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var source = new Uri(filePath);
|
||||
foreach (var manifest in manifests) {
|
||||
var swidTagObject = new Swidtag(manifest);
|
||||
|
||||
if (Swidtag.IsSwidtag(manifest) && swidTagObject.IsApplicable(new Hashtable())) {
|
||||
|
||||
return new Package(this, swidTagObject) {
|
||||
Location = source,
|
||||
Source = source.LocalPath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a package provider from a file path.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="requiredVersion"></param>
|
||||
/// <param name="minimumVersion"></param>
|
||||
/// <param name="maximumVersion"></param>
|
||||
internal void FindProviderFromFile(string name, string requiredVersion, string minimumVersion, string maximumVersion) {
|
||||
|
||||
// find the providers from the given Source location
|
||||
var pkgs = FindProviderByNameFromFile(name).Where(each => FilterOnName(each, name) && FilterOnVersion(each, requiredVersion, minimumVersion, maximumVersion)).ReEnumerable();
|
||||
|
||||
Debug("Total {0} providers found".format(pkgs.Count()));
|
||||
|
||||
//A user does not provide version info, we choose the latest
|
||||
if (!GetOptionValue("AllVersions").IsTrue() && (string.IsNullOrWhiteSpace(requiredVersion) && string.IsNullOrWhiteSpace(minimumVersion) && string.IsNullOrWhiteSpace(maximumVersion)))
|
||||
{
|
||||
pkgs = pkgs.GroupBy(p => p.Name).Select(each => each.OrderByDescending(pp => pp.Version).FirstOrDefault()).ReEnumerable();
|
||||
}
|
||||
|
||||
foreach (var package in pkgs)
|
||||
{
|
||||
YieldFromSwidtag(package, name);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Package> FindProviderByNameFromFile(string name) {
|
||||
foreach (var each in LocalSource) {
|
||||
// each can be file full path or folder directory
|
||||
var assemblies = System.IO.File.Exists(each) ? new[] {each} : System.IO.Directory.EnumerateFiles(each, "*.dll", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var item in assemblies) {
|
||||
yield return GetProviderFromFile(item, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterOnName(Package pkg, string name)
|
||||
{
|
||||
if (pkg == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WildcardPattern.ContainsWildcardCharacters(name))
|
||||
{
|
||||
// Applying the wildcard pattern matching
|
||||
const WildcardOptions wildcardOptions = WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase;
|
||||
var wildcardPattern = new WildcardPattern(name, wildcardOptions);
|
||||
|
||||
return wildcardPattern.IsMatch(pkg.Name);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return pkg.Name.EqualsIgnoreCase(name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterOnVersion(Package pkg, string requiredVersion, string minimumVersion, string maximumVersion) {
|
||||
|
||||
if(pkg == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requiredVersion) || (SoftwareIdentityVersionComparer.CompareVersions(pkg.VersionScheme, pkg.Version, requiredVersion) == 0))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(minimumVersion) || (SoftwareIdentityVersionComparer.CompareVersions(pkg.VersionScheme, pkg.Version, minimumVersion) >= 0))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(maximumVersion) || (SoftwareIdentityVersionComparer.CompareVersions(pkg.VersionScheme, pkg.Version, maximumVersion) <= 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetTempFileFullPath(string filePath) {
|
||||
if (string.IsNullOrWhiteSpace(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
// get a temp location
|
||||
var file = Path.Combine(Path.GetTempPath(), Path.GetFileName(filePath));
|
||||
|
||||
if (System.IO.File.Exists(file)) {
|
||||
//if exists already, delete it
|
||||
file.TryHardToDelete();
|
||||
}
|
||||
|
||||
// is that file still there?
|
||||
if (System.IO.File.Exists(file)) {
|
||||
//try it again if the generated file already exists
|
||||
file = GetTempFileFullPath(filePath);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private string CopyToTempLocation(string filePath) {
|
||||
if (string.IsNullOrWhiteSpace(filePath)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
var targetFile = GetTempFileFullPath(filePath);
|
||||
|
||||
if (filePath.EqualsIgnoreCase(targetFile)) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
Debug("Copying file '{0}' to '{1}'", filePath, targetFile);
|
||||
try {
|
||||
System.IO.File.Copy(filePath, targetFile);
|
||||
return targetFile;
|
||||
} catch (Exception ex) {
|
||||
Debug(ex.StackTrace);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
internal class Feed : Swid {
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Feed(BootstrapRequest request, Swidtag swidtag)
|
||||
: base(request, swidtag) {
|
||||
}
|
||||
|
||||
internal Feed(BootstrapRequest request, IEnumerable<Link> mirrors)
|
||||
: base(request, mirrors) {
|
||||
}
|
||||
|
||||
internal Feed(BootstrapRequest request, IEnumerable<Uri> mirrors)
|
||||
: base(request, mirrors) {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows the feed to find all the *declared latest* versions of packgaes
|
||||
/// </summary>
|
||||
/// <returns>A set of packages</returns>
|
||||
internal IEnumerable<Package> Query() {
|
||||
if (!IsValid) {
|
||||
return Enumerable.Empty<Package>();
|
||||
}
|
||||
|
||||
// first get all the packages that are marked as the latest version.
|
||||
var packages = Packages.Select(packageGroup => new Package(_request, packageGroup.Where(link => link.Attributes[Iso19770_2.Discovery.Latest].IsTrue())));
|
||||
|
||||
// then follow any supplemental links to more declared latest packages.
|
||||
var morePackages = More.SelectMany(nextGroup => new Feed(_request, nextGroup).Query());
|
||||
|
||||
// We do not follow to other feeds to find more, because declared latest packages should be in this feed (or a supplemental).
|
||||
return packages.Concat(morePackages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows the feed to find all versions of package matching 'name'
|
||||
/// </summary>
|
||||
/// <param name="name">the name or partial name of a package to find</param>
|
||||
/// <returns>A set of packages</returns>
|
||||
internal IEnumerable<Package> Query(string name) {
|
||||
if (!IsValid || string.IsNullOrEmpty(name)) {
|
||||
return Enumerable.Empty<Package>();
|
||||
}
|
||||
|
||||
// first get all the packages that are in this feed with a matched name
|
||||
var packages = PackagesFilteredByName(name).Select(packageGroup => new Package(_request, packageGroup)).Where(package => package.IsValid && package.Name.EqualsIgnoreCase(name));
|
||||
|
||||
// then follow any supplemental links to more declared latest packages.
|
||||
var morePackages = More.SelectMany(nextGroup => new Feed(_request, nextGroup).Query(name));
|
||||
|
||||
// let's search child feeds that declare that the name of the package in the feed matches the given name
|
||||
var packagesByName = Feeds.Where(feedGroup => feedGroup.Any(link => name.EqualsIgnoreCase(link.Attributes[Iso19770_2.Discovery.Name]))).SelectMany(feed => new Feed(_request, feed).Query(name));
|
||||
|
||||
// and search child feeds that the name would be in their range.
|
||||
var packagesByNameRange = Feeds.Where(feedGroup => feedGroup.Any(link => {
|
||||
var minName = link.Attributes[Iso19770_2.Discovery.MinimumName];
|
||||
var maxName = link.Attributes[Iso19770_2.Discovery.MaximumName];
|
||||
if (string.IsNullOrEmpty(minName) || string.IsNullOrEmpty(maxName)) {
|
||||
return false;
|
||||
}
|
||||
return (String.Compare(minName, name, StringComparison.OrdinalIgnoreCase) <= 0 && String.Compare(name, maxName, StringComparison.OrdinalIgnoreCase) <= 0);
|
||||
})).SelectMany(feed => new Feed(_request, feed).Query(name));
|
||||
|
||||
return packages.Concat(morePackages).Concat(packagesByName).Concat(packagesByNameRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows the feed to find the specific version of a package matching 'name'
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="version"></param>
|
||||
/// <returns>A set of packages</returns>
|
||||
internal IEnumerable<Package> Query(string name, string version) {
|
||||
if (string.IsNullOrEmpty(version)) {
|
||||
return Query(name);
|
||||
}
|
||||
|
||||
if (!IsValid || string.IsNullOrEmpty(name)) {
|
||||
return Enumerable.Empty<Package>();
|
||||
}
|
||||
|
||||
// first get all the packages that are in this feed with a matched name and version
|
||||
var packages = PackagesFilteredByName(name).Select(packageGroup => new Package(_request, packageGroup))
|
||||
.Where(package => package.IsValid && package.Name.EqualsIgnoreCase(name) && SoftwareIdentityVersionComparer.CompareVersions(package.VersionScheme, package.Version, version) == 0);
|
||||
|
||||
// then follow any supplemental links to more declared latest packages.
|
||||
var morePackages = More.SelectMany(nextGroup => new Feed(_request, nextGroup).Query(name, version));
|
||||
|
||||
// let's search child feeds that declare that the name of the package in the feed matches the given name
|
||||
// and the version is either in the specified range of the link, or there is no specified version.
|
||||
var packagesByName = Feeds.Where(feedGroup => feedGroup.Any(link => {
|
||||
if (name.EqualsIgnoreCase(link.Attributes[Iso19770_2.Discovery.Name])) {
|
||||
var minVer = link.Attributes[Iso19770_2.Discovery.MinimumVersion];
|
||||
if (!string.IsNullOrEmpty(minVer)) {
|
||||
// since we don't know the version scheme at this point, so we just have to guess.
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(Iso19770_2.VersionScheme.Unknown, minVer, version) > 0) {
|
||||
// the minimum version in the feed is greater than the specified version.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var maxVer = link.Attributes[Iso19770_2.Discovery.MaximumVersion];
|
||||
if (!string.IsNullOrEmpty(maxVer)) {
|
||||
// since we don't know the version scheme at this point, so we just have to guess.
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(Iso19770_2.VersionScheme.Unknown, version, maxVer) > 0) {
|
||||
// the given version is greater than the maximum version in the feed.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})).SelectMany(feed => new Feed(_request, feed).Query(name, version));
|
||||
|
||||
// and search child feeds that the name would be in their range.
|
||||
// (version matches have to wait till we Query() that feed, since name ranges and version ranges shouldn't be on the same link.)
|
||||
var packagesByNameRange = Feeds.Where(feedGroup => feedGroup.Any(link => {
|
||||
var minName = link.Attributes[Iso19770_2.Discovery.MinimumName];
|
||||
var maxName = link.Attributes[Iso19770_2.Discovery.MaximumName];
|
||||
if (string.IsNullOrEmpty(minName) || string.IsNullOrEmpty(maxName)) {
|
||||
return false;
|
||||
}
|
||||
return (String.Compare(minName, name, StringComparison.OrdinalIgnoreCase) <= 0 && String.Compare(name, maxName, StringComparison.OrdinalIgnoreCase) <= 0);
|
||||
})).SelectMany(feed => new Feed(_request, feed).Query(name, version));
|
||||
|
||||
return packages.Concat(morePackages).Concat(packagesByName).Concat(packagesByNameRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Follows the feed to find the all versions of a package matching 'name', in the given range
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="minimumVersion"></param>
|
||||
/// <param name="maximumVersion"></param>
|
||||
/// <returns>A set of packages</returns>
|
||||
internal IEnumerable<Package> Query(string name, string minimumVersion, string maximumVersion) {
|
||||
if (string.IsNullOrEmpty(minimumVersion) && string.IsNullOrEmpty(maximumVersion)) {
|
||||
return Query(name);
|
||||
}
|
||||
|
||||
if (!IsValid || string.IsNullOrEmpty(name)) {
|
||||
return Enumerable.Empty<Package>();
|
||||
}
|
||||
|
||||
// first get all the packages that are in this feed with a matched name and version
|
||||
var packages = PackagesFilteredByName(name).Select(packageGroup => new Package(_request, packageGroup)).Where(package => {
|
||||
if (package.IsValid && package.Name.EqualsIgnoreCase(name)) {
|
||||
if (!string.IsNullOrWhiteSpace(minimumVersion)) {
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(package.VersionScheme, package.Version, minimumVersion) < 0) {
|
||||
// a minimum version was specified, but the package version is less than the specified minimumversion.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(maximumVersion)) {
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(package.VersionScheme, package.Version, maximumVersion) > 0) {
|
||||
// a maximum version was specified, but the package version is more than the specified maximumversion.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// the version is in the range asked for.
|
||||
return true;
|
||||
}
|
||||
// not a valid package, or incorrect name.
|
||||
return false;
|
||||
});
|
||||
|
||||
// then follow any supplemental links to more declared latest packages.
|
||||
var morePackages = More.SelectMany(nextGroup => new Feed(_request, nextGroup).Query(name, minimumVersion, maximumVersion));
|
||||
|
||||
// let's search child feeds that declare that the name of the package in the feed matches the given name
|
||||
// and the version is either in the specified range of the link, or there is no specified version.
|
||||
var packagesByName = Feeds.Where(feedGroup => feedGroup.Any(link => {
|
||||
if (name.EqualsIgnoreCase(link.Attributes[Iso19770_2.Discovery.Name])) {
|
||||
// first, ensure that the requested miniumum version is lower than the maximum version found in the feed.
|
||||
var maxVer = link.Attributes[Iso19770_2.Discovery.MaximumVersion];
|
||||
if (!string.IsNullOrEmpty(maxVer)) {
|
||||
// since we don't know the version scheme at this point, so we just have to guess.
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(Iso19770_2.VersionScheme.Unknown, minimumVersion, maxVer) <= 0) {
|
||||
// the minimum version is greater than the maximum version in the feed.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// and then ensure that the requested maximum version is greater than the miniumum version found in the feed.
|
||||
var minVer = link.Attributes[Iso19770_2.Discovery.MinimumVersion];
|
||||
if (!string.IsNullOrEmpty(minVer)) {
|
||||
// since we don't know the version scheme at this point, so we just have to guess.
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions(Iso19770_2.VersionScheme.Unknown, maximumVersion, minVer) >= 0) {
|
||||
// the maximum version less than the minimum version in the feed.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})).SelectMany(feed => new Feed(_request, feed).Query(name, minimumVersion, maximumVersion));
|
||||
|
||||
// and search child feeds that the name would be in their range.
|
||||
// (version matches have to wait till we Query() that feed, since name ranges and version ranges shouldn't be on the same link.)
|
||||
var packagesByNameRange = Feeds.Where(feedGroup => feedGroup.Any(link => {
|
||||
var minName = link.Attributes[Iso19770_2.Discovery.MinimumName];
|
||||
var maxName = link.Attributes[Iso19770_2.Discovery.MaximumName];
|
||||
if (string.IsNullOrEmpty(minName) || string.IsNullOrEmpty(maxName)) {
|
||||
return false;
|
||||
}
|
||||
return (String.Compare(minName, name, StringComparison.OrdinalIgnoreCase) <= 0 && String.Compare(name, maxName, StringComparison.OrdinalIgnoreCase) <= 0);
|
||||
})).SelectMany(feed => new Feed(_request, feed).Query(name, minimumVersion, maximumVersion));
|
||||
|
||||
return packages.Concat(morePackages).Concat(packagesByName).Concat(packagesByNameRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using Packaging;
|
||||
|
||||
internal class Package : Swid {
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Package(BootstrapRequest request, Swidtag swidtag)
|
||||
: base(request, swidtag) {
|
||||
}
|
||||
|
||||
internal Package(BootstrapRequest request, IEnumerable<Link> mirrors) : base(request, mirrors) {
|
||||
}
|
||||
|
||||
internal Package(BootstrapRequest request, IEnumerable<Uri> mirrors)
|
||||
: base(request, mirrors) {
|
||||
}
|
||||
|
||||
internal string Name {
|
||||
get {
|
||||
if (IsValid) {
|
||||
return _swidtag.Name;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal string Version {
|
||||
get {
|
||||
if (IsValid) {
|
||||
return _swidtag.Version ?? "0";
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
|
||||
internal string VersionScheme {
|
||||
get {
|
||||
if (IsValid) {
|
||||
return _swidtag.VersionScheme ?? Iso19770_2.VersionScheme.Unknown;
|
||||
}
|
||||
return Iso19770_2.VersionScheme.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
internal string Source {get; set;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Api;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
using PackageManagement.Internal.Utility.Plugin;
|
||||
|
||||
internal class Swid {
|
||||
private const int SwidDownloadTimeout = 40000;
|
||||
internal readonly BootstrapRequest _request;
|
||||
internal readonly Swidtag _swidtag;
|
||||
private bool _timedOut;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Swid(BootstrapRequest request, Swidtag swidtag) {
|
||||
_request = request;
|
||||
_swidtag = swidtag;
|
||||
}
|
||||
|
||||
internal Swid(BootstrapRequest request, IEnumerable<Link> mirrors) {
|
||||
_request = request;
|
||||
_swidtag = DownloadSwidtag(mirrors);
|
||||
}
|
||||
|
||||
internal Swid(BootstrapRequest request, IEnumerable<Uri> mirrors) {
|
||||
_request = request;
|
||||
_swidtag = DownloadSwidtag(mirrors);
|
||||
}
|
||||
|
||||
internal Uri Location {get; set;}
|
||||
|
||||
private IRequest DownloadRequest {
|
||||
get {
|
||||
_timedOut = false;
|
||||
// overrides the Warning to check for timed out messages.
|
||||
return new object[] {
|
||||
new {
|
||||
Warning = new Func<string, bool>((messageText) => {
|
||||
if (messageText == Constants.Status.TimedOut) {
|
||||
_timedOut = true;
|
||||
return true;
|
||||
}
|
||||
return _request.Warning(messageText);
|
||||
}),
|
||||
Error = new Func<string,string,string,string, bool>((category, id, target, messageText ) => {
|
||||
// tag download errors don't count as "errors", at best, just a warning.
|
||||
return _request.Verbose(messageText);
|
||||
})
|
||||
},
|
||||
_request
|
||||
}.As<IRequest>();
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
protected IEnumerable<IGrouping<string, Link>> Artifacts {
|
||||
get {
|
||||
return IsValid ? _swidtag.Links.GroupBy(link => string.IsNullOrEmpty(link.Artifact) ? FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory() : link.Artifact) : Enumerable.Empty<IGrouping<string, Link>>();
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<IGrouping<string, Link>> Feeds {
|
||||
get {
|
||||
return IsValid
|
||||
? _swidtag.Links.Where(link => link.Relationship == Iso19770_2.Relationship.Feed).GroupBy(link => string.IsNullOrEmpty(link.Artifact) ? FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory() : link.Artifact) : Enumerable.Empty<IGrouping<string, Link>>();
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<IGrouping<string, Link>> Packages {
|
||||
get {
|
||||
return IsValid
|
||||
? _swidtag.Links.Where(link => link.Relationship == Iso19770_2.Relationship.Package).GroupBy(link => string.IsNullOrEmpty(link.Artifact) ? FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory() : link.Artifact)
|
||||
: Enumerable.Empty<IGrouping<string, Link>>();
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<IGrouping<string, Link>> More {
|
||||
get {
|
||||
return _swidtag.Links.Where(link => link.Relationship == Iso19770_2.Relationship.Supplemental).GroupBy(link => string.IsNullOrEmpty(link.Artifact) ? FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory() : link.Artifact);
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual bool IsValid {
|
||||
get {
|
||||
return _swidtag != null;
|
||||
}
|
||||
}
|
||||
|
||||
private Swidtag DownloadSwidtag(IEnumerable<Uri> locations) {
|
||||
foreach (var location in locations.WhereNotNull()) {
|
||||
try {
|
||||
var filename = FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory();
|
||||
DownloadSwidtagToFile(filename, location);
|
||||
|
||||
if (_timedOut) {
|
||||
// try one more time...
|
||||
DownloadSwidtagToFile(filename, location);
|
||||
}
|
||||
|
||||
if (filename.FileExists()) {
|
||||
try
|
||||
{
|
||||
var document = XDocument.Load(filename);
|
||||
if (Swidtag.IsSwidtag(document.Root))
|
||||
{
|
||||
Location = location;
|
||||
return new Swidtag(document);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.IO.File.Delete(filename);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.Dump();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DownloadSwidtagToFile(string filename, Uri location) {
|
||||
return _request.RetryDownload(
|
||||
(uri) =>
|
||||
{
|
||||
if (_request.ProviderServices == null)
|
||||
{
|
||||
// during initialization, the pluggable downloader isn't available.
|
||||
// luckily, it's built into this assembly, so we'll create and use it directly.
|
||||
return new WebDownloader().DownloadFile(location, filename, SwidDownloadTimeout, false, DownloadRequest.As<Request>());
|
||||
}
|
||||
|
||||
// otherwise, we can just use the pluggable one.
|
||||
return _request.ProviderServices.DownloadFile(location, filename, SwidDownloadTimeout, false, DownloadRequest);
|
||||
},
|
||||
location
|
||||
);
|
||||
}
|
||||
|
||||
internal Swidtag DownloadSwidtag(IEnumerable<Link> locations) {
|
||||
return DownloadSwidtag(locations.Where(link => link != null && link.HRef != null).Select(link => link.HRef));
|
||||
}
|
||||
|
||||
protected IEnumerable<IGrouping<string, Link>> PackagesFilteredByName(string name) {
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
return Packages;
|
||||
}
|
||||
|
||||
return Packages.Where(packageGroup => {
|
||||
var n = packageGroup.FirstOrDefault().Attributes[Iso19770_2.Discovery.Name];
|
||||
return (string.IsNullOrEmpty(n) || name.EqualsIgnoreCase(n));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Security.AccessControl;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Packaging;
|
||||
using PackageManagement.Internal.Utility.Platform;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
using Win32;
|
||||
using File = System.IO.File;
|
||||
|
||||
public class ProgramsProvider {
|
||||
/// <summary>
|
||||
/// The name of this Package Provider
|
||||
/// </summary>
|
||||
internal const string ProviderName = "Programs";
|
||||
|
||||
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the Provider.
|
||||
/// </summary>
|
||||
/// <returns>The name of this proivder (uses the constant declared at the top of the class)</returns>
|
||||
public string GetPackageProviderName() {
|
||||
return ProviderName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs one-time initialization of the PROVIDER.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void InitializeProvider(Request request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::InitializeProvider'", ProviderName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of strings to the client advertizing features this provider supports.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetFeatures(Request request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::GetFeatures' ", ProviderName);
|
||||
|
||||
foreach (var feature in _features) {
|
||||
request.Yield(feature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns dynamic option definitions to the HOST
|
||||
/// </summary>
|
||||
/// <param name="category">The category of dynamic options that the HOST is interested in</param>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetDynamicOptions(string category, Request request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
category = category ?? string.Empty;
|
||||
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::GetDynamicOptions' '{1}'", ProviderName, category);
|
||||
|
||||
switch (category.ToLowerInvariant()) {
|
||||
case "install":
|
||||
// options required for install/uninstall/getinstalledpackages
|
||||
request.YieldDynamicOption("IncludeWindowsInstaller", "Switch", false);
|
||||
request.YieldDynamicOption("IncludeSystemComponent", "Switch", false);
|
||||
break;
|
||||
|
||||
case "provider":
|
||||
// options used with this provider. Not currently used.
|
||||
break;
|
||||
|
||||
case "source":
|
||||
// options for package sources
|
||||
break;
|
||||
|
||||
case "package":
|
||||
// options used when searching for packages
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packages that are installed
|
||||
/// </summary>
|
||||
/// <param name="name">the package name to match. Empty or null means match everything</param>
|
||||
/// <param name="requiredVersion">the specific version asked for. If this parameter is specified (ie, not null or empty string) then the minimum and maximum values are ignored</param>
|
||||
/// <param name="minimumVersion">the minimum version of packages to return . If the <code>requiredVersion</code> parameter is specified (ie, not null or empty string) this should be ignored</param>
|
||||
/// <param name="maximumVersion">the maximum version of packages to return . If the <code>requiredVersion</code> parameter is specified (ie, not null or empty string) this should be ignored</param>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetInstalledPackages(string name, string requiredVersion, string minimumVersion, string maximumVersion, Request request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::GetInstalledPackages' '{1}','{2}','{3}','{4}'", ProviderName, name, requiredVersion, minimumVersion, maximumVersion);
|
||||
|
||||
// dump out results.
|
||||
|
||||
if (Environment.Is64BitOperatingSystem) {
|
||||
using (var hklm64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", RegistryKeyPermissionCheck.ReadSubTree, RegistryRights.ReadKey)) {
|
||||
if (!YieldPackages("hklm64", hklm64, name, requiredVersion, minimumVersion, maximumVersion, request)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (var hkcu64 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false)) {
|
||||
if (!YieldPackages("hkcu64", hkcu64, name, requiredVersion, minimumVersion, maximumVersion, request)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var hklm32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false)) {
|
||||
if (!YieldPackages("hklm32", hklm32, name, requiredVersion, minimumVersion, maximumVersion, request)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (var hkcu32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32).OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", false)) {
|
||||
if (!YieldPackages("hkcu32", hkcu32, name, requiredVersion, minimumVersion, maximumVersion, request)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool YieldPackages(string hive, RegistryKey regkey, string name, string requiredVersion, string minimumVersion, string maximumVersion, Request request) {
|
||||
if (regkey != null) {
|
||||
var includeWindowsInstaller = request.GetOptionValue("IncludeWindowsInstaller").IsTrue();
|
||||
var includeSystemComponent = request.GetOptionValue("IncludeSystemComponent").IsTrue();
|
||||
|
||||
var wildcardPattern = new WildcardPattern(name??"", WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase);
|
||||
|
||||
|
||||
foreach (var key in regkey.GetSubKeyNames()) {
|
||||
var subkey = regkey.OpenSubKey(key);
|
||||
if (subkey != null) {
|
||||
var properties = subkey.GetValueNames().ToDictionaryNicely(each => each.ToString(), each => (subkey.GetValue(each) ?? string.Empty).ToString(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!includeWindowsInstaller && properties.ContainsKey("WindowsInstaller") && properties["WindowsInstaller"] == "1") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!includeSystemComponent && properties.ContainsKey("SystemComponent") && properties["SystemComponent"] == "1") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var productName = "";
|
||||
|
||||
if (!properties.TryGetValue("DisplayName", out productName)) {
|
||||
// no product name?
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(productName) && (string.IsNullOrWhiteSpace(name) || wildcardPattern.IsMatch(productName))) {
|
||||
|
||||
var productVersion = properties.Get("DisplayVersion") ?? "";
|
||||
var publisher = properties.Get("Publisher") ?? "";
|
||||
var uninstallString = properties.Get("QuietUninstallString") ?? properties.Get("UninstallString") ?? "";
|
||||
var comments = properties.Get("Comments") ?? "";
|
||||
|
||||
var fp = hive + @"\" + subkey;
|
||||
|
||||
if (!string.IsNullOrEmpty(requiredVersion)) {
|
||||
if (SoftwareIdentityVersionComparer.CompareVersions("unknown", requiredVersion, productVersion) != 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (!string.IsNullOrEmpty(minimumVersion) && SoftwareIdentityVersionComparer.CompareVersions("unknown", productVersion, minimumVersion) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(maximumVersion) && SoftwareIdentityVersionComparer.CompareVersions("unknown", productVersion, maximumVersion) > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.YieldSoftwareIdentity(fp, productName, productVersion, "unknown", comments, "", name, "", "") != null) {
|
||||
if (properties.Keys.Where(each => !string.IsNullOrWhiteSpace(each)).Any(k => request.AddMetadata(fp, k.MakeSafeFileName(), properties[k]) == null)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
[assembly: AssemblyTitle("PackageManagement.CoreProviders")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("CoreProviders")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
||||
[assembly: Guid("cbb49c87-7eb3-4054-acfe-783d13dd9755")]
|
||||
@@ -0,0 +1,278 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.Providers.Internal
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Globalization;
|
||||
#if CORECLR
|
||||
using System.Net.Http;
|
||||
#endif
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
public class WebDownloader {
|
||||
internal static string ProviderName = "WebDownloader";
|
||||
|
||||
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> {
|
||||
{Constants.Features.SupportedSchemes, new[] {"http", "https", "ftp", "file"}},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of strings to the client advertizing features this provider supports.
|
||||
/// </summary>
|
||||
/// <param name="request">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetFeatures(Request request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
// Nice-to-have put a debug message in that tells what's going on.
|
||||
request.Debug("Calling '{0}::GetFeatures' ", ProviderName);
|
||||
foreach (var feature in _features) {
|
||||
request.Yield(feature);
|
||||
}
|
||||
}
|
||||
|
||||
public void InitializeProvider(Request request) {
|
||||
}
|
||||
|
||||
public string GetDownloaderName() {
|
||||
return ProviderName;
|
||||
}
|
||||
|
||||
public string DownloadFile(Uri remoteLocation, string localFilename, int timeoutMilliseconds, bool showProgress, Request request) {
|
||||
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
if (remoteLocation == null) {
|
||||
throw new ArgumentNullException("remoteLocation");
|
||||
}
|
||||
|
||||
request.Debug("Calling 'WebDownloader::DownloadFile' '{0}','{1}','{2}','{3}'", remoteLocation, localFilename, timeoutMilliseconds, showProgress);
|
||||
|
||||
if (remoteLocation.Scheme.ToLowerInvariant() != "http" && remoteLocation.Scheme.ToLowerInvariant() != "https" && remoteLocation.Scheme.ToLowerInvariant() != "ftp") {
|
||||
request.Error(ErrorCategory.InvalidResult, remoteLocation.ToString(), Constants.Messages.SchemeNotSupported, remoteLocation.Scheme);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (localFilename == null) {
|
||||
localFilename = FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory();
|
||||
}
|
||||
|
||||
localFilename = Path.GetFullPath(localFilename);
|
||||
|
||||
// did the caller pass us a directory name?
|
||||
if (Directory.Exists(localFilename)) {
|
||||
localFilename = Path.Combine(localFilename, "downloadedFile.tmp");
|
||||
}
|
||||
|
||||
// make sure that the parent folder is created first.
|
||||
var folder = Path.GetDirectoryName(localFilename);
|
||||
if (!Directory.Exists(folder)) {
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
|
||||
// clobber an existing file if it's already there.
|
||||
// todo: in the future, we could check the md5 of the file and if the remote server supports it
|
||||
// todo: we could skip the download.
|
||||
if (File.Exists(localFilename)) {
|
||||
localFilename.TryHardToDelete();
|
||||
}
|
||||
|
||||
// setup the progress tracker if the caller wanted one.
|
||||
int pid = 0;
|
||||
if (showProgress) {
|
||||
pid = request.StartProgress(0, "Downloading '{0}'", remoteLocation);
|
||||
}
|
||||
|
||||
#if CORECLR
|
||||
var task = Download(remoteLocation, localFilename, showProgress, request, pid);
|
||||
|
||||
task.Wait(timeoutMilliseconds);
|
||||
|
||||
if (!task.IsCompleted)
|
||||
{
|
||||
request.Warning(Constants.Status.TimedOut);
|
||||
request.Debug("Timed out downloading '{0}'", remoteLocation.AbsoluteUri);
|
||||
}
|
||||
#else
|
||||
var webClient = new WebClient();
|
||||
|
||||
// Mozilla/5.0 is the general token that says the browser is Mozilla compatible, and is common to almost every browser today.
|
||||
webClient.Headers.Add("User-Agent", "Mozilla/5.0 PackageManagement");
|
||||
|
||||
if (request.WebProxy != null)
|
||||
{
|
||||
// if user supplies webproxy, use that
|
||||
webClient.Proxy = request.WebProxy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// get ie settings
|
||||
webClient.Proxy = WebRequest.GetSystemWebProxy();
|
||||
|
||||
// set credentials to be user credentials
|
||||
webClient.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
|
||||
}
|
||||
|
||||
var done = new ManualResetEvent(false);
|
||||
|
||||
webClient.DownloadFileCompleted += (sender, args) => {
|
||||
if (args.Cancelled || args.Error != null) {
|
||||
localFilename = null;
|
||||
}
|
||||
done.Set();
|
||||
};
|
||||
|
||||
var lastPercent = 0;
|
||||
|
||||
if (showProgress) {
|
||||
webClient.DownloadProgressChanged += (sender, args) => {
|
||||
// Progress(requestObject, 2, (int)percent, "Downloading {0} of {1} bytes", args.BytesReceived, args.TotalBytesToReceive);
|
||||
var percent = (int)((args.BytesReceived*100)/args.TotalBytesToReceive);
|
||||
if (percent > lastPercent) {
|
||||
lastPercent = percent;
|
||||
request.Progress(pid, (int)((args.BytesReceived*100)/args.TotalBytesToReceive), "To {0}", localFilename);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// start the download
|
||||
webClient.DownloadFileAsync(remoteLocation, localFilename);
|
||||
|
||||
// wait for the completion
|
||||
if (timeoutMilliseconds > 0) {
|
||||
if (!done.WaitOne(timeoutMilliseconds))
|
||||
{
|
||||
webClient.CancelAsync();
|
||||
request.Warning(Constants.Status.TimedOut);
|
||||
request.Debug("Timed out downloading '{0}'", remoteLocation.AbsoluteUri);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// wait until it completes or fails on it's own
|
||||
done.WaitOne();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// if we don't have the file by this point, we've failed.
|
||||
if (localFilename == null || !File.Exists(localFilename)) {
|
||||
request.CompleteProgress(pid, false);
|
||||
request.Warning(Constants.Messages.UnableToDownload, remoteLocation.ToString(), localFilename);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
request.CompleteProgress(pid, true);
|
||||
}
|
||||
|
||||
return localFilename;
|
||||
}
|
||||
|
||||
#if CORECLR
|
||||
private async Task<string> Download(Uri remoteLocation, string localFilename, bool showProgress, Request request, int pid)
|
||||
{
|
||||
var clientHandler = new HttpClientHandler();
|
||||
|
||||
clientHandler.UseDefaultCredentials = true;
|
||||
|
||||
// if user supplies web proxy, use that
|
||||
if (request.WebProxy != null)
|
||||
{
|
||||
clientHandler.Proxy = request.WebProxy;
|
||||
}
|
||||
|
||||
var httpClient = new HttpClient(clientHandler);
|
||||
|
||||
request.Debug("Calling httpclient with remotelocation {0}", remoteLocation.AbsoluteUri);
|
||||
|
||||
// Mozilla/5.0 is the general token that says the browser is Mozilla compatible, and is common to almost every browser today.
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 PackageManagement");
|
||||
httpClient.DefaultRequestHeaders.ExpectContinue = false;
|
||||
|
||||
long totalBytesToReceive = 0L;
|
||||
|
||||
HttpResponseMessage response = await httpClient.GetAsync(remoteLocation, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.Content != null && response.Content.Headers != null)
|
||||
{
|
||||
totalBytesToReceive = response.Content.Headers.ContentLength ?? 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Stream input = await response.Content.ReadAsStreamAsync();
|
||||
byte[] bytes = new byte[1024 * 4];
|
||||
FileStream output = File.Open(localFilename, FileMode.OpenOrCreate);
|
||||
|
||||
long totalDownloaded = 0;
|
||||
int current = 0;
|
||||
int lastPercent = 0;
|
||||
|
||||
current = await input.ReadAsync(bytes, 0, bytes.Length);
|
||||
|
||||
while (current > 0)
|
||||
{
|
||||
totalDownloaded += current;
|
||||
|
||||
await output.WriteAsync(bytes, 0, current);
|
||||
|
||||
if (showProgress && totalBytesToReceive != 0)
|
||||
{
|
||||
int percent = (int)((totalDownloaded * 100) / totalBytesToReceive);
|
||||
|
||||
if (percent > lastPercent)
|
||||
{
|
||||
lastPercent = percent;
|
||||
request.Progress(pid, (int)percent, "To {0}", localFilename);
|
||||
}
|
||||
}
|
||||
|
||||
current = await input.ReadAsync(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
input.Dispose();
|
||||
output.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
request.Debug(e.Message);
|
||||
localFilename = null;
|
||||
}
|
||||
|
||||
return localFilename;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"monad/src/oneget/providers/inbox/coreproviders/ProgramsProvider.cs": "ProgramsProvider.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/WebDownloader.cs": "WebDownloader.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/provider.manifest": "provider.manifest",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Properties/AssemblyInfo.cs": "Properties/AssemblyInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Resources/Messages.resx": "resources/Microsoft.PackageManagement.Providers.Resources.Messages.resx",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Bootstrap/BootstrapProvider.cs": "Bootstrap/BootstrapProvider.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Bootstrap/BootstrapRequest.cs": "Bootstrap/BootstrapRequest.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Bootstrap/Feed.cs": "Bootstrap/Feed.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Bootstrap/Package.cs": "Bootstrap/Package.cs",
|
||||
"monad/src/oneget/providers/inbox/coreproviders/Bootstrap/Swid.cs": "Bootstrap/Swid.cs"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "Microsoft.PackageManagement.CoreProviders",
|
||||
"version": "1.0.0-*",
|
||||
"authors": ["quoct"],
|
||||
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
|
||||
"configurations": {
|
||||
"Linux": {
|
||||
"buildOptions": {
|
||||
"define": ["LINUX"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"System.Management.Automation": "1.0.0-*",
|
||||
"Microsoft.PackageManagement": "1.0.0-*"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": "",
|
||||
"System.Numerics": "",
|
||||
"System.Data": "",
|
||||
"System.DirectoryServices": "",
|
||||
"System.Security": "",
|
||||
"System.Transactions": "",
|
||||
"System.Runtime.Serialization": "",
|
||||
"System.Management": "",
|
||||
"System.Configuration": "",
|
||||
"System.Configuration.Install": "",
|
||||
"System.Net": "",
|
||||
"System.Net.Http": "",
|
||||
"System.IO.Compression": "",
|
||||
"System.IO.Compression.FileSystem": ""
|
||||
}
|
||||
},
|
||||
|
||||
"netstandard1.5": {
|
||||
"imports": ["dnxcore50"],
|
||||
"buildOptions": {
|
||||
"define": ["CORECLR"],
|
||||
"compile": {
|
||||
"exclude": [ "ProgramsProvider.cs"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<SoftwareIdentity
|
||||
name="Core Providers for PackageManagement"
|
||||
version="1.0.0.0" versionScheme="multipartnumeric"
|
||||
media="(windows) and (PackageManagement) and (min-PackageManagement.api:2)"
|
||||
xmlns="http://standards.iso.org/iso/19770/-2/2015/schema.xsd">
|
||||
|
||||
</SoftwareIdentity>
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="FailedToExtractAndCopy" xml:space="preserve">
|
||||
<value>Fail to extract and copy to '{0}'.</value>
|
||||
<comment>0 - The directory we are copying to</comment>
|
||||
</data>
|
||||
<data name="FindingPackage" xml:space="preserve">
|
||||
<value>Finding the package '{0}'.</value>
|
||||
<comment>0 - package name</comment>
|
||||
</data>
|
||||
<data name="InstalledPackage" xml:space="preserve">
|
||||
<value>Installed the package '{0}' to '{1}'.</value>
|
||||
<comment>0 - package name; 1 - file directory</comment>
|
||||
</data>
|
||||
<data name="InstallingPackage" xml:space="preserve">
|
||||
<value>Installing the package '{0}'.</value>
|
||||
<comment>0 - package name</comment>
|
||||
</data>
|
||||
<data name="InvalidFileType" xml:space="preserve">
|
||||
<value>Install-PackageProvider -Source only supports '{0}' for file-based installation. '{1}' is not a supported file type. To install a PowerShell-based provider, you must install it from a PSRepository.</value>
|
||||
</data>
|
||||
<data name="MissingInstallationmedia" xml:space="preserve">
|
||||
<value>Provider '{0}' missing installationmedia to install.</value>
|
||||
</data>
|
||||
<data name="MissingProviderManifest" xml:space="preserve">
|
||||
<value>'{0}' does not contain a package provider manifest. Refer to 'Get-Help Import-PackageProvider' for guidance.</value>
|
||||
</data>
|
||||
<data name="MissingVersion" xml:space="preserve">
|
||||
<value>The version is not found for the specified provider '{0}'.</value>
|
||||
<comment>0 - package name</comment>
|
||||
</data>
|
||||
<data name="MoreThanOneDllExists" xml:space="preserve">
|
||||
<value>More than 1 dll with provider manifest exists in provider '{0}'.</value>
|
||||
<comment>0 - package name</comment>
|
||||
</data>
|
||||
<data name="NoDependencyPackageFound" xml:space="preserve">
|
||||
<value>Cannot find dependency packages from link '{0}'.</value>
|
||||
<comment>0 - uri link</comment>
|
||||
</data>
|
||||
<data name="OnlyHttpsSchemeSupported" xml:space="preserve">
|
||||
<value>Link '{0}' is not using https.</value>
|
||||
<comment>0 - Uri link</comment>
|
||||
</data>
|
||||
<data name="ProviderBootstrapFailed" xml:space="preserve">
|
||||
<value>Unable to bootstrap the required package provider due to problems with network connectivity. Please fix your network connection. If this is not possible, refer to 'Get-Help Install-PackageProvider' or http://go.microsoft.com/fwlink/?LinkId=626941 for guidance on installing the package provider manually.</value>
|
||||
</data>
|
||||
<data name="RetryDownload" xml:space="preserve">
|
||||
<value>Cannot download link '{0}', retrying for '{1}' more times.</value>
|
||||
<comment>0 - Uri link, 1 - number of retries</comment>
|
||||
</data>
|
||||
<data name="UseLocalSource" xml:space="preserve">
|
||||
<value>Searching for providers in the file path {0}.</value>
|
||||
<comment>0 - file folder or file name</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
internal static class Constants {
|
||||
internal const string ScriptStackTrace = "MSG:ScriptStackTrace";
|
||||
internal const string ProviderNameUnknown = "(unknown)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
public class Dependency {
|
||||
public string ProviderName {get; set;}
|
||||
public string PackageName {get; set;}
|
||||
public string Version {get; set;}
|
||||
public string Source {get; set;}
|
||||
public string AppliesTo {get; set;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Internal;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
public class DynamicOption : Yieldable {
|
||||
public DynamicOption(OptionCategory category, string name, OptionType expectedType, bool isRequired, IEnumerable<object> permittedValues) {
|
||||
Name = name;
|
||||
ExpectedType = expectedType;
|
||||
IsRequired = isRequired;
|
||||
PermittedValues = permittedValues;
|
||||
}
|
||||
|
||||
public DynamicOption(string name, OptionType expectedType, bool isRequired, IEnumerable<object> permittedValues) {
|
||||
Name = name;
|
||||
ExpectedType = expectedType;
|
||||
IsRequired = isRequired;
|
||||
PermittedValues = permittedValues;
|
||||
}
|
||||
|
||||
|
||||
public DynamicOption(OptionCategory category,string name, OptionType expectedType, bool isRequired) : this(category, name , expectedType, isRequired, null) {
|
||||
}
|
||||
|
||||
public DynamicOption(string name, OptionType expectedType, bool isRequired)
|
||||
: this(name, expectedType, isRequired, null) {
|
||||
}
|
||||
|
||||
|
||||
public DynamicOption() {
|
||||
}
|
||||
|
||||
public string Name {get; set;}
|
||||
public OptionType ExpectedType {get; set;}
|
||||
public bool IsRequired {get; set;}
|
||||
public IEnumerable<object> PermittedValues {get; set;}
|
||||
|
||||
public override bool YieldResult(PsRequest r) {
|
||||
if (r == null) {
|
||||
throw new ArgumentNullException("r");
|
||||
}
|
||||
return r.YieldDynamicOption(Name, ExpectedType.ToString(), IsRequired) && PermittedValues.WhereNotNull().Select(each => each.ToString()).ToArray().All(v => r.YieldKeyValuePair(Name, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
public class Entity {
|
||||
public string Name {get; set;}
|
||||
public string RegId {get; set;}
|
||||
public string Role {get; set;}
|
||||
|
||||
public IEnumerable<string> Roles {
|
||||
get {
|
||||
return (string.IsNullOrWhiteSpace(Role) ? "unknown" : Role).Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
set
|
||||
{
|
||||
var values = value == null ? new string[0] : value.ToArray();
|
||||
Role = ( values.Length == 0) ? "unknown" : value.JoinWith(" ");
|
||||
}
|
||||
}
|
||||
|
||||
public string Thumbprint {get; set;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
public enum OptionCategory {
|
||||
Package = 0,
|
||||
Provider = 1,
|
||||
Source = 2,
|
||||
Install = 3
|
||||
}
|
||||
|
||||
public enum OptionType {
|
||||
String = 0,
|
||||
StringArray = 1,
|
||||
Int = 2,
|
||||
Switch = 3,
|
||||
Folder = 4,
|
||||
File = 5,
|
||||
Path = 6,
|
||||
Uri = 7,
|
||||
SecureString = 8
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Internal;
|
||||
|
||||
public class Feature : Yieldable {
|
||||
private KeyValuePair<string, string[]> _pair;
|
||||
|
||||
public Feature(string name) : this(name, new string[0]) {
|
||||
}
|
||||
|
||||
public Feature(string name, string value) : this(name, new[] {value}) {
|
||||
}
|
||||
|
||||
public Feature(string name, string[] values) {
|
||||
_pair = new KeyValuePair<string, string[]>(name, values);
|
||||
}
|
||||
|
||||
public override bool YieldResult(PsRequest r) {
|
||||
if (r == null) {
|
||||
throw new ArgumentNullException("r");
|
||||
}
|
||||
if (_pair.Value.Length == 0) {
|
||||
return r.YieldKeyValuePair(_pair.Key, null);
|
||||
}
|
||||
return _pair.Value.All(each => r.YieldKeyValuePair(_pair.Key, each));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
public class Link {
|
||||
public string HRef {get; set;}
|
||||
public string Relationship {get; set;}
|
||||
public string MediaType {get; set;}
|
||||
public string Ownership {get; set;}
|
||||
public string Use {get; set;}
|
||||
public string AppliesToMedia {get; set;}
|
||||
public string Artifact {get; set;}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Internal;
|
||||
|
||||
public class PackageSource : Yieldable {
|
||||
public PackageSource(string name, string location, bool isTrusted, bool isRegistered, bool isValidated) {
|
||||
Name = name;
|
||||
Location = location;
|
||||
IsTrusted = isTrusted;
|
||||
IsRegistered = isRegistered;
|
||||
IsValidated = isValidated;
|
||||
}
|
||||
|
||||
public PackageSource(string name, string location, bool isTrusted, bool isRegistered, bool isValidated, Hashtable details)
|
||||
: this(name, location, isTrusted, isRegistered, isValidated) {
|
||||
_details = details;
|
||||
}
|
||||
|
||||
|
||||
public PackageSource(string name, string location, bool isTrusted, bool isRegistered, Hashtable details)
|
||||
: this(name, location, isTrusted, isRegistered,false) {
|
||||
_details = details;
|
||||
}
|
||||
|
||||
public string Name {get; set;}
|
||||
public string Location {get; set;}
|
||||
public bool IsTrusted {get; set;}
|
||||
public bool IsRegistered { get; set; }
|
||||
|
||||
public bool IsValidated {get; set;}
|
||||
public override bool YieldResult(PsRequest r) {
|
||||
if (r == null) {
|
||||
throw new ArgumentNullException("r");
|
||||
}
|
||||
|
||||
return r.YieldPackageSource(Name, Location, IsTrusted, IsRegistered,IsValidated) && YieldDetails(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
using Microsoft.PackageManagement.Internal.Utility.Platform;
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Platform;
|
||||
|
||||
internal static class PowerShellExtensions {
|
||||
internal static PowerShell Clear(this PowerShell powershell) {
|
||||
if (powershell != null) {
|
||||
powershell.WaitForReady();
|
||||
powershell.Commands = new PSCommand();
|
||||
}
|
||||
return powershell;
|
||||
}
|
||||
|
||||
internal static PSModuleInfo ImportModule(this PowerShell powershell, string name, bool force = false) {
|
||||
if (powershell != null) {
|
||||
powershell.Clear().AddCommand("Import-Module");
|
||||
powershell.AddParameter("Name", name);
|
||||
powershell.AddParameter("PassThru");
|
||||
|
||||
if (force) {
|
||||
powershell.AddParameter("Force");
|
||||
}
|
||||
return powershell.Invoke<PSModuleInfo>().ToArray().FirstOrDefault();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static IEnumerable<PSModuleInfo> TestModuleManifest(this PowerShell powershell, string path) {
|
||||
if (powershell != null) {
|
||||
return powershell
|
||||
.Clear()
|
||||
.AddCommand("Test-ModuleManifest")
|
||||
.AddParameter("Path", path)
|
||||
.Invoke<PSModuleInfo>().ToArray();
|
||||
}
|
||||
return Enumerable.Empty<PSModuleInfo>();
|
||||
}
|
||||
|
||||
internal static IEnumerable<PSModuleInfo> GetModule(this PowerShell powershell, string moduleName)
|
||||
{
|
||||
if ((powershell != null) && (!string.IsNullOrWhiteSpace(moduleName)))
|
||||
{
|
||||
return powershell
|
||||
.Clear()
|
||||
.AddCommand("Get-Module")
|
||||
.AddParameter("Name", moduleName)
|
||||
.AddParameter("ListAvailable")
|
||||
.Invoke<PSModuleInfo>();
|
||||
}
|
||||
return Enumerable.Empty<PSModuleInfo>();
|
||||
}
|
||||
|
||||
internal static PowerShell SetVariable(this PowerShell powershell, string variable, object value) {
|
||||
if (powershell != null) {
|
||||
powershell
|
||||
.Clear()
|
||||
.AddCommand("Set-Variable")
|
||||
.AddParameter("Name", variable)
|
||||
.AddParameter("Value", value)
|
||||
.Invoke();
|
||||
}
|
||||
return powershell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the last output to the user. This will be null if the operation did not succeed.
|
||||
/// outputAction is the action that will be performed whenever an object is outputted to the powershell pipeline
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="powershell"></param>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="outputAction"></param>
|
||||
/// <param name="errorAction"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
internal static T InvokeFunction<T>(this PowerShell powershell, string command,
|
||||
EventHandler<DataAddedEventArgs> outputAction, EventHandler<DataAddedEventArgs> errorAction, params object[] args) {
|
||||
|
||||
if (powershell != null) {
|
||||
powershell.Clear().AddCommand(command);
|
||||
foreach (var arg in args) {
|
||||
powershell.AddArgument(arg);
|
||||
}
|
||||
#if DEBUG
|
||||
NativeMethods.OutputDebugString("[Cmdlet:debugging] -- InvokeFunction ({0}, {1})".format(command, args.Select( each => (each ?? "<NULL>").ToString()).JoinWithComma(), powershell.InvocationStateInfo.Reason));
|
||||
#endif
|
||||
|
||||
var input = new PSDataCollection<PSObject>();
|
||||
input.Complete();
|
||||
|
||||
var output = new PSDataCollection<PSObject>();
|
||||
|
||||
if (outputAction != null)
|
||||
{
|
||||
output.DataAdded += outputAction;
|
||||
}
|
||||
|
||||
if (errorAction != null)
|
||||
{
|
||||
powershell.Streams.Error.DataAdded += errorAction;
|
||||
}
|
||||
|
||||
powershell.Invoke(null, output, new PSInvocationSettings());
|
||||
|
||||
if (output.Count == 0)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
// return the last output to the user
|
||||
PSObject last = output.Last();
|
||||
|
||||
if (last != null)
|
||||
{
|
||||
// convert last to T
|
||||
return (T)last.ImmediateBaseObject;
|
||||
}
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
internal static PowerShell WaitForReady(this PowerShell powershell) {
|
||||
try {
|
||||
if (powershell != null) {
|
||||
switch (powershell.InvocationStateInfo.State) {
|
||||
case PSInvocationState.Stopping:
|
||||
while (powershell.InvocationStateInfo.State == PSInvocationState.Stopping) {
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
break;
|
||||
|
||||
case PSInvocationState.Running:
|
||||
powershell.Stop();
|
||||
while (powershell.InvocationStateInfo.State == PSInvocationState.Stopping) {
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
break;
|
||||
|
||||
case PSInvocationState.Failed:
|
||||
break;
|
||||
case PSInvocationState.Completed:
|
||||
break;
|
||||
|
||||
case PSInvocationState.Stopped:
|
||||
break;
|
||||
|
||||
case PSInvocationState.NotStarted:
|
||||
break;
|
||||
case PSInvocationState.Disconnected:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.Dump();
|
||||
}
|
||||
return powershell;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Versions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Collections;
|
||||
using System.Globalization;
|
||||
using Implementation;
|
||||
using PackageManagement.Internal;
|
||||
using PackageManagement.Internal.Implementation;
|
||||
using PackageManagement.Internal.Providers;
|
||||
using PackageManagement.Internal.Utility.Plugin;
|
||||
using ErrorCategory = System.Management.Automation.ErrorCategory;
|
||||
using ProviderOption = PackageManagement.Internal.Implementation.PackageManagementService.ProviderOption;
|
||||
|
||||
internal class ProviderItem {
|
||||
internal PSModuleInfo ModuleInfo;
|
||||
internal string ProviderPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A MetaProvider class that loads Providers implemented as a PowerShell Module.
|
||||
/// It connects the functions in the PowerShell module to the expected functions that the
|
||||
/// interface expects.
|
||||
/// </summary>
|
||||
public class PowerShellMetaProvider : IDisposable {
|
||||
private static readonly HashSet<string> _exclusionList = new HashSet<string> {
|
||||
"AppBackgroundTask",
|
||||
"AppLocker",
|
||||
"Appx",
|
||||
"AssignedAccess",
|
||||
"BitLocker",
|
||||
"BitsTransfer",
|
||||
"BranchCache",
|
||||
"CimCmdlets",
|
||||
"Defender",
|
||||
"DirectAccessClientComponents",
|
||||
"Dism",
|
||||
"DnsClient",
|
||||
"Hyper-V",
|
||||
"International",
|
||||
"iSCSI",
|
||||
"ISE",
|
||||
"Kds",
|
||||
"Microsoft.PowerShell.Diagnostics",
|
||||
"Microsoft.PowerShell.Host",
|
||||
"Microsoft.PowerShell.Management",
|
||||
"Microsoft.PowerShell.Security",
|
||||
"Microsoft.PowerShell.Utility",
|
||||
"Microsoft.WSMan.Management",
|
||||
"MMAgent",
|
||||
"MsDtc",
|
||||
"NetAdapter",
|
||||
"NetConnection",
|
||||
"NetEventPacketCapture",
|
||||
"NetLbfo",
|
||||
"NetNat",
|
||||
"NetQos",
|
||||
"NetSecurity",
|
||||
"NetSwitchTeam",
|
||||
"NetTCPIP",
|
||||
"NetWNV",
|
||||
"NetworkConnectivityStatus",
|
||||
"NetworkTransition",
|
||||
"PcsvDevice",
|
||||
"PKI",
|
||||
"PrintManagement",
|
||||
"PSDiagnostics",
|
||||
"PSScheduledJob",
|
||||
"PSWorkflow",
|
||||
"PSWorkflowUtility",
|
||||
"ScheduledTasks",
|
||||
"SecureBoot",
|
||||
"SmbShare",
|
||||
"SmbWitness",
|
||||
"StartScreen",
|
||||
"Storage",
|
||||
"TLS",
|
||||
"TroubleshootingPack",
|
||||
"TrustedPlatformModule",
|
||||
"VpnClient",
|
||||
"Wdac",
|
||||
"WindowsDeveloperLicense",
|
||||
"WindowsErrorReporting",
|
||||
"WindowsSearch",
|
||||
"PackageManagement", // dont' search ourselves.
|
||||
"OneGet", // dont' search ourselves.
|
||||
"OneGet-Edge" // dont' search ourselves.
|
||||
};
|
||||
|
||||
private static string _baseFolder;
|
||||
private static string _powershellProviderFunctionsPath;
|
||||
|
||||
//The reason of using 'object' instead of' PowerShellPackageProvider' is that PowerShellPackageProvider is a provider
|
||||
//that is not visiable to the PackageManagment.
|
||||
private readonly IDictionary<string, object> _availableProviders = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly IDictionary<string, List<ProviderItem>> _psProviderCacheTable = new Dictionary<string, List<ProviderItem>>(StringComparer.OrdinalIgnoreCase);
|
||||
internal const string PowerShellGet = "PowerShellGet";
|
||||
private static readonly PowerShell _powershell = PowerShell.Create();
|
||||
private static bool _initialized = false;
|
||||
|
||||
public PowerShellMetaProvider() {
|
||||
// _packageProviders.BlockingEnumerator = true;
|
||||
}
|
||||
|
||||
internal static string BaseFolder {
|
||||
get {
|
||||
if (_baseFolder == null) {
|
||||
#if CORECLR
|
||||
_baseFolder = Path.GetDirectoryName(Path.GetFullPath(typeof(PowerShellMetaProvider).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName));
|
||||
#else
|
||||
_baseFolder = Path.GetDirectoryName(Path.GetFullPath(Assembly.GetExecutingAssembly().Location));
|
||||
#endif
|
||||
if (_baseFolder == null || !Directory.Exists(_baseFolder)) {
|
||||
throw new Exception(Resources.Messages.CantFindBasePowerShellModuleFolder);
|
||||
}
|
||||
}
|
||||
return _baseFolder;
|
||||
}
|
||||
}
|
||||
|
||||
static PowerShellMetaProvider() {
|
||||
try {
|
||||
EnsurePowerShellInitialized();
|
||||
} catch {
|
||||
//we capture the exceptions here so that we won't fail to load the metaprovider assembly
|
||||
//exceptions can happen when execution-policy is set to restrict, for example.
|
||||
}
|
||||
}
|
||||
|
||||
static void EnsurePowerShellInitialized() {
|
||||
if (!_initialized) {
|
||||
_powershell.ImportModule(PowerShellProviderFunctions);
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string PowerShellProviderFunctions {
|
||||
get {
|
||||
if (_powershellProviderFunctionsPath == null) {
|
||||
// try the etc directory
|
||||
_powershellProviderFunctionsPath = Path.Combine(BaseFolder, "etc", "PackageProviderFunctions.psm1");
|
||||
if (!File.Exists(_powershellProviderFunctionsPath)) {
|
||||
// fall back to the same directory.
|
||||
_powershellProviderFunctionsPath = Path.Combine(BaseFolder, "PackageProviderFunctions.psm1");
|
||||
if (!File.Exists(_powershellProviderFunctionsPath)) {
|
||||
// oh-oh, no powershell functions file.
|
||||
throw new Exception(String.Format(CultureInfo.CurrentCulture, Resources.Messages.UnableToFindPowerShellFunctionsFile, _powershellProviderFunctionsPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
return _powershellProviderFunctionsPath;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> ProviderNames {
|
||||
get {
|
||||
//return _packageProviders.Select(each => each.GetPackageProviderName());;
|
||||
return _availableProviders.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddToTable(string name, ProviderItem provider) {
|
||||
//try to find if the provider is in the table already
|
||||
|
||||
if (_psProviderCacheTable.ContainsKey(name)) {
|
||||
var list = _psProviderCacheTable[name];
|
||||
|
||||
var index = list.FindIndex(each => (each.ModuleInfo.Version == provider.ModuleInfo.Version) && (each.ProviderPath.EqualsIgnoreCase(provider.ProviderPath)));
|
||||
|
||||
if (index != -1) {
|
||||
list[index] = provider;
|
||||
|
||||
} else {
|
||||
_psProviderCacheTable[name].Add(provider);
|
||||
}
|
||||
} else {
|
||||
var entry = new List<ProviderItem> {
|
||||
provider
|
||||
};
|
||||
_psProviderCacheTable.Add(name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of this MetaProvider class
|
||||
/// </summary>
|
||||
public string MetaProviderName {
|
||||
get {
|
||||
return "PowerShell";
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public string GetProviderPath(string providername) {
|
||||
return _availableProviders.Keys.Where(each => each.EqualsIgnoreCase(providername))
|
||||
.Select(each => ((PowerShellPackageProvider)_availableProviders[each]).ModulePath).FirstOrDefault();
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string,PSModuleInfo>> ScanPrivateDataForProviders(PsRequest request, string baseFolder, Hashtable privateData, PSModuleInfo moduleInfo) {
|
||||
var providers = privateData.GetStringCollection("PackageManagementProviders").ReEnumerable();
|
||||
if (providers.Any()) {
|
||||
// found a module that is advertizing one or more Providers.
|
||||
|
||||
foreach (var provider in providers) {
|
||||
var fullPath = provider;
|
||||
try {
|
||||
if (!Path.IsPathRooted(provider)) {
|
||||
fullPath = Path.GetFullPath(Path.Combine(baseFolder, provider));
|
||||
}
|
||||
} catch {
|
||||
// got an error from the path.
|
||||
continue;
|
||||
}
|
||||
if (Directory.Exists(fullPath) || File.Exists(fullPath)) {
|
||||
// looks like we have something that could definitely be a
|
||||
// a module path.
|
||||
var result = new KeyValuePair<string, PSModuleInfo>(fullPath, moduleInfo);
|
||||
AddToPowerShellProviderCacheTable(result);
|
||||
yield return result;
|
||||
} else {
|
||||
request.Verbose(string.Format(CultureInfo.CurrentCulture, Resources.Messages.FileNotFound, fullPath));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
request.Debug(string.Format(Resources.Messages.PackageManagementProvidersNotFound, baseFolder));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, PSModuleInfo>> GetPackageManagementModules(PsRequest request, PSModuleInfo module, Version requiredVersion, Version minimumVersion, Version maximumVersion) {
|
||||
// skip modules that we know don't contain any PM modules
|
||||
if (!_exclusionList.Contains(module.Name)) {
|
||||
var privateData = module.PrivateData as Hashtable;
|
||||
if (privateData != null) {
|
||||
if (requiredVersion != null) {
|
||||
if ((FourPartVersion)module.Version == (FourPartVersion)requiredVersion) {
|
||||
return ScanPrivateDataForProviders(request, Path.GetDirectoryName(module.Path), privateData, module).ToArray();
|
||||
} else {
|
||||
return Enumerable.Empty<KeyValuePair<string, PSModuleInfo>>();
|
||||
}
|
||||
}
|
||||
|
||||
if ((minimumVersion != null) && ((FourPartVersion)module.Version < (FourPartVersion)minimumVersion)) {
|
||||
return Enumerable.Empty<KeyValuePair<string, PSModuleInfo>>();
|
||||
}
|
||||
|
||||
if ((maximumVersion != null) && ((FourPartVersion)module.Version > (FourPartVersion)maximumVersion)) {
|
||||
return Enumerable.Empty<KeyValuePair<string, PSModuleInfo>>();
|
||||
}
|
||||
|
||||
return ScanPrivateDataForProviders(request, Path.GetDirectoryName(module.Path), privateData, module).ToArray();
|
||||
}
|
||||
}
|
||||
return Enumerable.Empty<KeyValuePair<string, PSModuleInfo>>();
|
||||
}
|
||||
|
||||
private void AddToPowerShellProviderCacheTable(KeyValuePair<string, PSModuleInfo> moduleInfo) {
|
||||
|
||||
//some times when PrivateData in a provider's .psd1 meta file contains multiple providers (not recommanded way),
|
||||
//they all reside under the same module path. So we extract each file name and add to the table as dictionary key
|
||||
//to indicate they are actaully different providers.
|
||||
if (moduleInfo.Key != null) {
|
||||
var name = Path.GetFileNameWithoutExtension(moduleInfo.Key);
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for the case where provider is a dll, we enforce that provider name must be the same as module name
|
||||
// so the name we add to the hash table will be the name of the module.
|
||||
// (for the case where provider is not a dll, we still allow multiple provider names for a module)
|
||||
if (string.Equals(Path.GetExtension(moduleInfo.Key), ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
name = moduleInfo.Value.Name;
|
||||
}
|
||||
|
||||
AddToTable(name,
|
||||
new ProviderItem {
|
||||
ModuleInfo = moduleInfo.Value,
|
||||
ProviderPath = moduleInfo.Key,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static PackageManagementService PackageManagementService {
|
||||
get {
|
||||
return PackageManager.Instance as PackageManagementService;
|
||||
}
|
||||
}
|
||||
|
||||
//key = path, value = PSModuleInfo
|
||||
private IEnumerable<KeyValuePair<string, PSModuleInfo>> ScanForModules(
|
||||
PsRequest request,
|
||||
Version requiredVersion,
|
||||
Version minimumVersion,
|
||||
Version maximumVersion,
|
||||
ProviderOption providerOption = ProviderOption.LatestVersion) {
|
||||
|
||||
// two places we search for modules
|
||||
// 1. in this assembly's folder, look for all psd1 and psm1 files.
|
||||
//
|
||||
// 2. modules in the PSMODULEPATH
|
||||
//
|
||||
// Import each one of those, and check to see if they have a PackageManagementProviders section in their private data
|
||||
|
||||
var allAvailableModules = _powershell
|
||||
.Clear()
|
||||
.AddCommand("Get-Module")
|
||||
.AddParameter("ListAvailable")
|
||||
.Invoke<PSModuleInfo>();
|
||||
|
||||
return allAvailableModules.WhereNotNull()
|
||||
.SelectMany(each => GetPackageManagementModules(request, each, requiredVersion, minimumVersion, maximumVersion));
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, PSModuleInfo>> ScanForPowerShellGetModule(PsRequest request)
|
||||
{
|
||||
var psget = _powershell.GetModule("PowerShellGet").FirstOrDefault();
|
||||
return psget != null ? GetPackageManagementModules(request, psget, null, null, null) : Enumerable.Empty<KeyValuePair<string, PSModuleInfo>>();
|
||||
}
|
||||
|
||||
private IEnumerable<string> AlternativeModuleScan(
|
||||
PsRequest request,
|
||||
Version requiredVersion,
|
||||
Version minimumVersion,
|
||||
Version maximumVersion,
|
||||
ProviderOption providerOption = ProviderOption.LatestVersion) {
|
||||
|
||||
var psModulePath = Environment.GetEnvironmentVariable("PSModulePath") ?? "";
|
||||
|
||||
IEnumerable<string> paths = psModulePath.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// add assumed paths just in case the environment variable isn't really set.
|
||||
try {
|
||||
#if CORECLR
|
||||
paths = paths.ConcatSingleItem(Path.Combine(Environment.GetEnvironmentVariable("windir"), "system32", @"WindowsPowerShell\v1.0\Modules"));
|
||||
#else
|
||||
paths = paths.ConcatSingleItem(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"WindowsPowerShell\v1.0\Modules"));
|
||||
#endif
|
||||
} catch {
|
||||
// skip the folder if it's not valid
|
||||
}
|
||||
try {
|
||||
#if CORECLR
|
||||
paths = paths.ConcatSingleItem(Path.Combine(Environment.GetEnvironmentVariable("userprofile"), "documents", @"WindowsPowerShell\Modules"));
|
||||
#else
|
||||
paths = paths.ConcatSingleItem(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"WindowsPowerShell\Modules"));
|
||||
#endif
|
||||
} catch {
|
||||
// skip the folder if it's not valid
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(BaseFolder) && BaseFolder.DirectoryExists()) {
|
||||
paths = paths.ConcatSingleItem(BaseFolder);
|
||||
}
|
||||
|
||||
var moduleFolders = paths.Distinct(new PathEqualityComparer(PathCompareOption.Full)).Where(each => each.DirectoryExists()).SelectMany(each => Directory.EnumerateDirectories(each).Where(dir => !_exclusionList.Contains(Path.GetFileName(dir))));
|
||||
|
||||
foreach (var module in moduleFolders) {
|
||||
|
||||
var moduleManifest = Path.Combine(module, Path.GetFileName(module) + ".psd1");
|
||||
if (File.Exists(moduleManifest)) {
|
||||
//The version check is defered in the GetPackageManagementModules() because we do not know the version without looking into the content.
|
||||
yield return moduleManifest;
|
||||
}
|
||||
|
||||
//The following are the cases where there are multiple modules installed on the system. The file folder is the module version.
|
||||
var selectedVersions = Directory.EnumerateDirectories(module).Select(dir => new {
|
||||
folder = dir,
|
||||
ver = (FourPartVersion)Path.GetFileName(dir)
|
||||
}).Where(each => each.ver > 0L);
|
||||
|
||||
if (requiredVersion != null) {
|
||||
var version = selectedVersions.Where(each => each.ver == (FourPartVersion)requiredVersion).Select(each => each.folder).FirstOrDefault();
|
||||
if (version != null) {
|
||||
var file = Path.Combine(version, Path.GetFileName(Path.GetFileName(module)) + ".psd1");
|
||||
if (File.Exists(file)) {
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minimumVersion != null) {
|
||||
selectedVersions = selectedVersions.Where(each => each.ver >= (FourPartVersion)minimumVersion);
|
||||
|
||||
}
|
||||
if (maximumVersion != null) {
|
||||
selectedVersions = selectedVersions.Where(each => each.ver <= (FourPartVersion)maximumVersion);
|
||||
}
|
||||
|
||||
var results = (providerOption == PackageManagementService.ProviderOption.AllProvider) ?
|
||||
selectedVersions.Select(version => Path.Combine(version.folder, Path.GetFileName(Path.GetFileName(module)) + ".psd1")).Where(File.Exists) :
|
||||
new[] {
|
||||
selectedVersions.OrderByDescending(each => each.ver)
|
||||
.Select(version => Path.Combine(version.folder, Path.GetFileName(Path.GetFileName(module)) + ".psd1"))
|
||||
.FirstOrDefault(File.Exists)
|
||||
};
|
||||
|
||||
foreach (var result in results.WhereNotNull()) {
|
||||
yield return result;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object CreateProvider(string name) {
|
||||
|
||||
if (_availableProviders.ContainsKey(name)) {
|
||||
var provider = _availableProviders[name];
|
||||
|
||||
if (provider != null) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
// it's possible that this is a path passed in. Let's see if it's a provider.
|
||||
if (!string.IsNullOrEmpty(name) && name.FileExists()) {
|
||||
// MUST DO: load provider from filepath.
|
||||
}
|
||||
// create the instance
|
||||
throw new Exception("No provider by name '{0}' registered.".format(name));
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
_availableProviders.Clear();
|
||||
_psProviderCacheTable.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private PowerShellPackageProvider Create(PsRequest request, string modulePath, string requiredVersion, bool force, bool logWarning) {
|
||||
var ps = PowerShell.Create();
|
||||
try {
|
||||
// load the powershell provider functions into this runspace.
|
||||
if (ps.ImportModule(PowerShellProviderFunctions, false) != null) {
|
||||
var result = ps.ImportModule(modulePath, force);
|
||||
if (result != null) {
|
||||
try {
|
||||
return new PowerShellPackageProvider(ps, result, requiredVersion);
|
||||
} catch (Exception e) {
|
||||
e.Dump();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// something didn't go well.
|
||||
// skip it.
|
||||
e.Dump();
|
||||
if (logWarning) {
|
||||
request.Warning(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// didn't import correctly.
|
||||
ps.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public void InitializeProvider(PsRequest request) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
request.Debug("Initializing PowerShell MetaProvider");
|
||||
|
||||
//During the initialization, we load the PowerShellGet only to speeding up a little bit
|
||||
var psModules = ScanForPowerShellGetModule(request).WhereNotNull();
|
||||
|
||||
foreach (var psModule in psModules) {
|
||||
//Check if the PowerShellGet provider exists
|
||||
if ((psModule.Key != null) && (psModule.Value != null)) {
|
||||
//load the PowerShellGet
|
||||
AnalyzeModule(request, psModule.Key, psModule.Value.Version ?? new Version(0, 0), false, true, psModule.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (_availableProviders.ContainsKey(PowerShellGet))
|
||||
{
|
||||
request.Debug("Loaded PowerShell Provider: PowerShellGet");
|
||||
}
|
||||
else
|
||||
{
|
||||
//if we can not load PowerShellGet, we do not fail the initialization
|
||||
request.Verbose(string.Format(CultureInfo.CurrentCulture, Resources.Messages.ModuleNotFound, PowerShellGet));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void AddToPackageProviderCacheTable(PackageProvider provider)
|
||||
{
|
||||
PackageManagementService.AddToProviderCacheTable(provider.ProviderName, provider);
|
||||
}
|
||||
|
||||
|
||||
public void RefreshProviders(PsRequest request, string providerName, Version requiredVersion, Version minimumVersion, Version maximumVersion, bool logWarning) {
|
||||
//find and load the latest versions of the providers if only providerName exists, e.g., get-pp -name or import-pp -name
|
||||
//find and load the particular provider if both providerName and version are provided
|
||||
_psProviderCacheTable.Clear();
|
||||
|
||||
EnsurePowerShellInitialized();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(providerName)) {
|
||||
//Get the list of available providers
|
||||
var modules = ScanForModules(request, requiredVersion, minimumVersion, maximumVersion, ProviderOption.AllProvider).ReEnumerable();
|
||||
|
||||
var tasks = modules.AsyncForEach(modulePath => AnalyzeModule(request, modulePath.Key, modulePath.Value.Version ?? new Version(0, 0), false, logWarning, modulePath.Value));
|
||||
tasks.WaitAll();
|
||||
} else {
|
||||
//find all providers but only load the lastest if no name nor version exists, e.g. get-pp -list
|
||||
|
||||
//Scan for the all available providers
|
||||
var results = ScanForModules(request, null, null, null, ProviderOption.AllProvider).ToArray();
|
||||
|
||||
if (!_psProviderCacheTable.Any()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var list in _psProviderCacheTable.Values.WhereNotNull()) {
|
||||
var psInfo = list.OrderByDescending(each => each.ModuleInfo.Version).ToArray();
|
||||
if (!psInfo.Any()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PackageProvider pkgProvider = null;
|
||||
for (var index = 0; index < psInfo.Length; index++) {
|
||||
var providerItem = psInfo[index];
|
||||
if (providerItem == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if the provider is a dll, we will just provide a default provider, assuming the module name will be the name of the provider and add to the cache table (if it is not already loaded)
|
||||
if (string.Equals(Path.GetExtension(providerItem.ProviderPath), ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// check whether it is already loaded or not
|
||||
var loadedProvider = PackageManagementService.PackageProviders.FirstOrDefault(item => string.Equals(item.ProviderPath, providerItem.ProviderPath, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// only provide default provider if it is not loaded
|
||||
if (loadedProvider == null)
|
||||
{
|
||||
// here moduleinfo.providercategory is the module name.
|
||||
// we are applying the guideline that module name is the same as provider name so we use that as the provider name
|
||||
AddToPackageProviderCacheTable(new PackageProvider(new DefaultPackageProvider(providerItem.ModuleInfo.Name, providerItem.ModuleInfo.Version.ToString()))
|
||||
{
|
||||
ProviderPath = providerItem.ProviderPath,
|
||||
Version = providerItem.ModuleInfo.Version,
|
||||
IsLoaded = false
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
//load the provider that has the latest version
|
||||
if (pkgProvider == null) {
|
||||
// analyze the module
|
||||
pkgProvider = AnalyzeModule(request, providerItem.ProviderPath, providerItem.ModuleInfo.Version, false, logWarning, providerItem.ModuleInfo);
|
||||
} else {
|
||||
//the rest of providers under the same module will just create a provider object for the output but not loaded
|
||||
var packageProvider = new PackageProvider(new DefaultPackageProvider(pkgProvider.ProviderName, providerItem.ModuleInfo.Version.ToString()))
|
||||
{
|
||||
ProviderPath = providerItem.ProviderPath,
|
||||
Version = providerItem.ModuleInfo.Version,
|
||||
IsLoaded = false
|
||||
};
|
||||
AddToPackageProviderCacheTable(packageProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_psProviderCacheTable.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PackageProvider> FindMatchedProvidersFromInternalCacheTable(PsRequest request, string providerPath) {
|
||||
//Search from the internal table to see if we can the matched provider
|
||||
|
||||
var providers = PackageManagementService.ProviderCacheTable
|
||||
.Select(each => each.Value).Select(list => list
|
||||
.Where(each => each.ProviderPath.EqualsIgnoreCase(providerPath) && each.IsLoaded)
|
||||
.Select(each => each));
|
||||
|
||||
return providers.SelectMany(list => {
|
||||
var packageProviders = list as PackageProvider[] ?? list.ToArray();
|
||||
return packageProviders;
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<object> LoadAvailableProvider(PsRequest request, string modulePath, Version requiredVersion, bool force) {
|
||||
if (request == null) {
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(modulePath)) {
|
||||
throw new ArgumentNullException("modulePath");
|
||||
}
|
||||
|
||||
EnsurePowerShellInitialized();
|
||||
|
||||
//Check if it is already in the cache table
|
||||
var providersAlreadyImported = FindMatchedProvidersFromInternalCacheTable(request, modulePath).ToArray();
|
||||
|
||||
if (providersAlreadyImported.Any() && !force) {
|
||||
return providersAlreadyImported;
|
||||
}
|
||||
|
||||
//Trying to load it from the path directly
|
||||
var pkgProvider = AnalyzeModule(request, modulePath, requiredVersion ?? new Version(0, 0), force);
|
||||
if (pkgProvider != null) {
|
||||
return new[] { pkgProvider }.WhereNotNull();
|
||||
}
|
||||
|
||||
request.Error(PackageManagement.Internal.Constants.Messages.FailedToImportProvider,
|
||||
ErrorCategory.InvalidOperation.ToString(), PowerShellGet, string.Format(CultureInfo.CurrentCulture, Resources.Messages.FailedToImportProvider, modulePath));
|
||||
return Enumerable.Empty<PackageProvider>();
|
||||
}
|
||||
|
||||
private PackageProvider AnalyzeModule(PsRequest request, string modulePath, Version requiredVersion, bool force, bool logWarning =true, PSModuleInfo psModuleInfo = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modulePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
request.Debug("Attempting to load PowerShell Provider Module [{0}]", modulePath);
|
||||
|
||||
if (string.Equals(Path.GetExtension(modulePath), ".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (psModuleInfo != null)
|
||||
{
|
||||
// fake provider and returns it
|
||||
var result = new PackageProvider(new DefaultPackageProvider(psModuleInfo.Name, psModuleInfo.Version.ToString()))
|
||||
{
|
||||
ProviderPath = modulePath,
|
||||
Version = psModuleInfo.Version,
|
||||
IsLoaded = false
|
||||
};
|
||||
|
||||
AddToPackageProviderCacheTable(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// psmoduleinfo is only null when this function is called in loadavailableprovider
|
||||
// but in that case we want to load the provider directly anyway so we can do this
|
||||
// if the path is a .dll then we ask packagemanagement to load it for us
|
||||
// it will also register the dll
|
||||
PackageManagementService.LoadProviderAssembly(request, modulePath, true);
|
||||
|
||||
// now let's checked whether we can find it in the list of loaded providers
|
||||
foreach (var loadedProvider in PackageManagementService.PackageProviders)
|
||||
{
|
||||
// the one loaded should have the same path
|
||||
if (string.Equals(loadedProvider.ProviderPath, modulePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return loadedProvider;
|
||||
}
|
||||
}
|
||||
|
||||
// if we reached here then we have failed to load the provider :(
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string requiredVersionString = requiredVersion.ToString();
|
||||
var provider = Create(request, modulePath, requiredVersionString, force, logWarning);
|
||||
if (provider != null) {
|
||||
var providerName = provider.GetPackageProviderName();
|
||||
if (!string.IsNullOrWhiteSpace(providerName)) {
|
||||
request.Debug(string.Format(CultureInfo.CurrentCulture, Resources.Messages.SuccessfullyLoadedModule, modulePath));
|
||||
// looks good to me, let's add this to the list of moduels this meta provider can create.
|
||||
|
||||
var packageProvider = new PackageProvider(provider.As<IPackageProvider>()) {
|
||||
IsLoaded = true,
|
||||
Version = provider.GetProviderVersion(),
|
||||
ProviderPath = modulePath
|
||||
};
|
||||
|
||||
// take out powershell get
|
||||
var psgetprovider = PackageManagementService.PackageProviders.FirstOrDefault(pv => string.Equals(pv.ProviderName, PowerShellGet, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (psModuleInfo != null)
|
||||
{
|
||||
// add swidtag information using moduleinfo
|
||||
// however, this won't give us as much information yet
|
||||
// we may have to fill this up later
|
||||
ProvideSwidTagInformation(packageProvider, psModuleInfo);
|
||||
}
|
||||
|
||||
AddToPackageProviderCacheTable(packageProvider);
|
||||
_availableProviders.AddOrSet(providerName, provider);
|
||||
|
||||
return packageProvider;
|
||||
} else {
|
||||
provider.Dispose();
|
||||
provider = null;
|
||||
request.Debug(string.Format(CultureInfo.CurrentCulture, Resources.Messages.ProviderNameIsNullOrEmpty, modulePath));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we cannot use psget to get swidtag information, we will try to fill in some of the information
|
||||
/// </summary>
|
||||
/// <param name="packageProvider"></param>
|
||||
/// <param name="psModuleInfo"></param>
|
||||
private void ProvideSwidTagInformation(PackageProvider packageProvider, PSModuleInfo psModuleInfo)
|
||||
{
|
||||
if (packageProvider == null || psModuleInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packageProvider.VersionScheme = "MultiPartNumeric";
|
||||
|
||||
Microsoft.PackageManagement.Internal.Packaging.SoftwareMetadata softwareMetadata = new Microsoft.PackageManagement.Internal.Packaging.SoftwareMetadata();
|
||||
bool changed = false;
|
||||
|
||||
var type = psModuleInfo.GetType();
|
||||
|
||||
// introduced in ps 2.0
|
||||
if (!string.IsNullOrWhiteSpace(psModuleInfo.Description))
|
||||
{
|
||||
softwareMetadata.Description = psModuleInfo.Description;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// introduced in ps 3.0
|
||||
if (!string.IsNullOrWhiteSpace(psModuleInfo.Copyright))
|
||||
{
|
||||
softwareMetadata.AddAttribute("copyright", psModuleInfo.Copyright);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// tags is introduced in ps 5.0
|
||||
var tagsProperty = type.GetProperty("Tags");
|
||||
bool isV5 = tagsProperty != null;
|
||||
|
||||
if (isV5)
|
||||
{
|
||||
// introduced in ps 5.0
|
||||
var tags = tagsProperty.GetValue(psModuleInfo);
|
||||
|
||||
// check that we have something in tags
|
||||
if (tags is IEnumerable<string> && (tags as IEnumerable<string>).Any())
|
||||
{
|
||||
softwareMetadata.AddAttribute("tags", string.Join(" ", (tags as IEnumerable<string>).Distinct()));
|
||||
changed = true;
|
||||
}
|
||||
|
||||
var releaseNotes = type.GetProperty("ReleaseNotes").GetValue(psModuleInfo);
|
||||
|
||||
// check that we have something in releasenotes
|
||||
if (releaseNotes is string && !string.IsNullOrWhiteSpace(type.GetProperty("ReleaseNotes").GetValue(psModuleInfo) as string))
|
||||
{
|
||||
softwareMetadata.AddAttribute("tags", string.Join(" ", (tags as IEnumerable<string>).Distinct()));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
packageProvider.AddElement(softwareMetadata);
|
||||
}
|
||||
|
||||
if (isV5)
|
||||
{
|
||||
var iconUri = type.GetProperty("IconUri").GetValue(psModuleInfo);
|
||||
|
||||
// introduced in ps 5.0
|
||||
if (iconUri is Uri)
|
||||
{
|
||||
packageProvider.AddLink(iconUri as Uri, "icon");
|
||||
}
|
||||
|
||||
var licenseUri = type.GetProperty("LicenseUri").GetValue(psModuleInfo);
|
||||
|
||||
// introduced in ps 5.0
|
||||
if (licenseUri is Uri)
|
||||
{
|
||||
packageProvider.AddLink(licenseUri as Uri, "license");
|
||||
}
|
||||
|
||||
var projectUri = type.GetProperty("ProjectUri").GetValue(psModuleInfo);
|
||||
|
||||
// introduced in ps 5.0
|
||||
if (projectUri is Uri)
|
||||
{
|
||||
packageProvider.AddLink(projectUri as Uri, "project");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// introduced in ps 3.0
|
||||
if (!string.IsNullOrWhiteSpace(psModuleInfo.Author))
|
||||
{
|
||||
packageProvider.AddEntity(psModuleInfo.Author, null, "author");
|
||||
}
|
||||
|
||||
// introduced in ps 3.0
|
||||
if (!string.IsNullOrWhiteSpace(psModuleInfo.CompanyName))
|
||||
{
|
||||
packageProvider.AddEntity(psModuleInfo.CompanyName, null, "owner");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
|
||||
using Packaging;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Versions;
|
||||
using Microsoft.PackageManagement.Internal.Api;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Collections;
|
||||
|
||||
public class PowerShellPackageProvider : PowerShellProviderBase {
|
||||
private static int _findId = 1;
|
||||
private readonly Lazy<Dictionary<int, List<string, string, string, string>>> _findByNameBatches = new Lazy<Dictionary<int, List<string, string, string, string>>>(() => new Dictionary<int, List<string, string, string, string>>());
|
||||
private readonly Lazy<Dictionary<int, List<string>>> _findByFileBatches = new Lazy<Dictionary<int, List<string>>>(() => new Dictionary<int, List<string>>());
|
||||
private readonly Lazy<Dictionary<int, List<Uri>>> _findByUriBatches = new Lazy<Dictionary<int, List<Uri>>>(() => new Dictionary<int, List<Uri>>());
|
||||
private string _version;
|
||||
|
||||
public PowerShellPackageProvider(PowerShell ps, PSModuleInfo module, string version) : base(ps, module) {
|
||||
_version = version;
|
||||
}
|
||||
|
||||
private bool IsFirstParameterType<T>(string function) {
|
||||
var method = GetMethod(function);
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return method.Parameters.Values.First().ParameterType == typeof (T);
|
||||
}
|
||||
|
||||
public bool IsMethodImplemented(string methodName) {
|
||||
if (methodName == null) {
|
||||
throw new ArgumentNullException("methodName");
|
||||
}
|
||||
|
||||
if (methodName.EqualsIgnoreCase("startfind") || methodName.EqualsIgnoreCase("completeFind") || methodName.EqualsIgnoreCase("GetProviderVersion")) {
|
||||
return true;
|
||||
}
|
||||
#if DEEP_DEBUG
|
||||
var r = GetMethod(methodName) != null;
|
||||
if (!r) {
|
||||
Debug.WriteLine(" -> '{0}' Not Found In PowerShell Module '{1}'".format(methodName, _module.Name));
|
||||
}
|
||||
return r;
|
||||
#else
|
||||
return GetMethod(methodName) != null;
|
||||
#endif
|
||||
}
|
||||
|
||||
private object Call(string function, IRequest requestObject, params object[] args) {
|
||||
return PsRequest.New(requestObject, this, function).CallPowerShell(args);
|
||||
}
|
||||
|
||||
#region implement PackageProvider-interface
|
||||
|
||||
public void AddPackageSource(string name, string location, bool trusted, IRequest requestObject) {
|
||||
Call("AddPackageSource", requestObject, name, location, trusted);
|
||||
}
|
||||
public void FindPackage(string name, string requiredVersion, string minimumVersion, string maximumVersion, int id, IRequest requestObject) {
|
||||
// special case.
|
||||
// if FindPackage is implemented taking an array of strings
|
||||
// and the id > 0 then we need to hold onto the collection until
|
||||
// CompleteFind is called.
|
||||
|
||||
// if it expects multiples...
|
||||
if (IsFirstParameterType<string[]>("FindPackage")) {
|
||||
if (id > 0) {
|
||||
_findByNameBatches.Value.GetOrAdd(id, () => new List<string, string, string, string>()).Add(name, requiredVersion, minimumVersion, maximumVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// not passed in as a set.
|
||||
Call("FindPackage", requestObject, new string[] {
|
||||
name
|
||||
}, requiredVersion, minimumVersion, maximumVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, it has to take them one at a time and yield them anyway.
|
||||
Call("FindPackage",requestObject, name, requiredVersion, minimumVersion, maximumVersion);
|
||||
}
|
||||
public void FindPackageByFile(string file, int id, IRequest requestObject) {
|
||||
// special case.
|
||||
// if FindPackageByFile is implemented taking an array of strings
|
||||
// and the id > 0 then we need to hold onto the collection until
|
||||
// CompleteFind is called.
|
||||
|
||||
// if it expects multiples...
|
||||
if (IsFirstParameterType<string[]>("FindPackageByFile")) {
|
||||
if (id > 0) {
|
||||
_findByFileBatches.Value.GetOrAdd(id, () => new List<string>()).Add(file);
|
||||
return;
|
||||
}
|
||||
// not passed in as a set.
|
||||
Call("FindPackageByFile",requestObject, new string[] {
|
||||
file
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Call("FindPackageByFile", requestObject, file);
|
||||
return;
|
||||
}
|
||||
public void FindPackageByUri(Uri uri, int id, IRequest requestObject) {
|
||||
// special case.
|
||||
// if FindPackageByUri is implemented taking an array of strings
|
||||
// and the id > 0 then we need to hold onto the collection until
|
||||
// CompleteFind is called.
|
||||
|
||||
// if it expects multiples...
|
||||
if (IsFirstParameterType<string[]>("FindPackageByUri")) {
|
||||
if (id > 0) {
|
||||
_findByUriBatches.Value.GetOrAdd(id, () => new List<Uri>()).Add(uri);
|
||||
return;
|
||||
}
|
||||
// not passed in as a set.
|
||||
Call("FindPackageByUri",requestObject, new Uri[] {
|
||||
uri
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Call("FindPackageByUri",requestObject, uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the packages that are installed
|
||||
/// </summary>
|
||||
/// <param name="name">the package name to match. Empty or null means match everything</param>
|
||||
/// <param name="requiredVersion">the specific version asked for. If this parameter is specified (ie, not null or empty string) then the minimum and maximum values are ignored</param>
|
||||
/// <param name="minimumVersion">the minimum version of packages to return . If the <code>requiredVersion</code> parameter is specified (ie, not null or empty string) this should be ignored</param>
|
||||
/// <param name="maximumVersion">the maximum version of packages to return . If the <code>requiredVersion</code> parameter is specified (ie, not null or empty string) this should be ignored</param>
|
||||
/// <param name="requestObject">
|
||||
/// An object passed in from the CORE that contains functions that can be used to interact with
|
||||
/// the CORE and HOST
|
||||
/// </param>
|
||||
public void GetInstalledPackages(string name, string requiredVersion, string minimumVersion, string maximumVersion, IRequest requestObject) {
|
||||
Call("GetInstalledPackages",requestObject, name,requiredVersion,minimumVersion,maximumVersion);
|
||||
}
|
||||
|
||||
public void GetDynamicOptions(string category, IRequest requestObject) {
|
||||
OptionCategory cat;
|
||||
if (Enum.TryParse(category ?? "", true, out cat)) {
|
||||
// if this version of the plugin doesn't support that category
|
||||
// there's no point in trying to get options from that type.
|
||||
Call("GetDynamicOptions", requestObject, cat);
|
||||
}
|
||||
}
|
||||
|
||||
private string _providerName;
|
||||
/// <summary>
|
||||
/// Returns the name of the Provider.
|
||||
/// </summary>
|
||||
/// <required />
|
||||
/// <returns>the name of the package provider</returns>
|
||||
public string GetPackageProviderName() {
|
||||
return _providerName ?? (_providerName = (string)CallPowerShellWithoutRequest("GetPackageProviderName"));
|
||||
}
|
||||
public void ResolvePackageSources(IRequest requestObject) {
|
||||
Call("ResolvePackageSources", requestObject);
|
||||
}
|
||||
|
||||
public void InitializeProvider(IRequest requestObject) {
|
||||
Call("InitializeProvider", requestObject);
|
||||
}
|
||||
|
||||
public string GetProviderVersion() {
|
||||
var result= (string)CallPowerShellWithoutRequest("GetProviderVersion");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result)) {
|
||||
|
||||
if (!string.IsNullOrEmpty(_version)) {
|
||||
return _version;
|
||||
}
|
||||
|
||||
if (_module.Version != new Version(0, 0, 0, 0)) {
|
||||
result = _module.Version.ToString();
|
||||
} else {
|
||||
try
|
||||
{
|
||||
// use the latest date as a version number
|
||||
return (FourPartVersion) _module.FileList.Max(each => new FileInfo(each).LastWriteTime);
|
||||
} catch {
|
||||
// I give up.
|
||||
return "0.0.0.1";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public void InstallPackage(string fastPath, IRequest requestObject) {
|
||||
Call("InstallPackage", requestObject, fastPath);
|
||||
}
|
||||
public void RemovePackageSource(string name, IRequest requestObject) {
|
||||
Call("RemovePackageSource", requestObject, name);
|
||||
}
|
||||
public void UninstallPackage(string fastPath, IRequest requestObject) {
|
||||
Call("UninstallPackage", requestObject, fastPath);
|
||||
}
|
||||
public void GetFeatures(IRequest requestObject) {
|
||||
Call("GetFeatures", requestObject);
|
||||
}
|
||||
|
||||
// --- operations on a package ---------------------------------------------------------------------------------------------------
|
||||
public void DownloadPackage(string fastPath, string location, IRequest requestObject) {
|
||||
Call("DownloadPackage", requestObject, fastPath, location);
|
||||
}
|
||||
|
||||
public void GetPackageDetails(string fastPath, IRequest requestObject) {
|
||||
Call("GetPackageDetails", requestObject, fastPath);
|
||||
}
|
||||
public int StartFind(IRequest requestObject) {
|
||||
lock (this) {
|
||||
return ++_findId;
|
||||
}
|
||||
}
|
||||
public void CompleteFind(int id, IRequest requestObject) {
|
||||
if (id < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_findByNameBatches.IsValueCreated) {
|
||||
var nameBatch = _findByNameBatches.Value.TryPullValue(id);
|
||||
if (nameBatch != null) {
|
||||
if (IsFirstParameterType<string[]>("FindPackage")) {
|
||||
// it takes a batch at a time.
|
||||
|
||||
var names = nameBatch.Select(each => each.Item1).ToArray();
|
||||
var p1 = nameBatch[0];
|
||||
|
||||
Call("FindPackage", requestObject,names, p1.Item2, p1.Item3, p1.Item4);
|
||||
} else {
|
||||
foreach (var each in nameBatch) {
|
||||
Call("FindPackage",requestObject, each.Item1, each.Item2, each.Item3, each.Item4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_findByFileBatches.IsValueCreated) {
|
||||
var fileBatch = _findByFileBatches.Value.TryPullValue(id);
|
||||
if (fileBatch != null) {
|
||||
if (IsFirstParameterType<string[]>("FindPackageByFile")) {
|
||||
// it takes a batch at a time.
|
||||
Call("FindPackageByFile", requestObject, new object[] {
|
||||
fileBatch.ToArray()
|
||||
});
|
||||
} else {
|
||||
foreach (var each in fileBatch) {
|
||||
Call("FindPackageByFile",requestObject, each);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_findByUriBatches.IsValueCreated) {
|
||||
var uriBatch = _findByUriBatches.Value.TryPullValue(id);
|
||||
if (uriBatch != null) {
|
||||
if (IsFirstParameterType<string[]>("FindPackageByUri")) {
|
||||
// it takes a batch at a time.
|
||||
Call("FindPackageByUri",requestObject, new object[] {uriBatch.ToArray()});
|
||||
} else {
|
||||
foreach (var each in uriBatch) {
|
||||
Call("FindPackageByUri",requestObject, each);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
using Microsoft.PackageManagement.Internal.Utility.Platform;
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Threading;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Platform;
|
||||
using Messages = Microsoft.PackageManagement.MetaProvider.PowerShell.Resources.Messages;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
public class PowerShellProviderBase : IDisposable {
|
||||
private object _lock = new Object();
|
||||
protected PSModuleInfo _module;
|
||||
private PowerShell _powershell;
|
||||
private ManualResetEvent _reentrancyLock = new ManualResetEvent(true);
|
||||
private readonly Dictionary<string, CommandInfo> _allCommands = new Dictionary<string, CommandInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, CommandInfo> _methods = new Dictionary<string, CommandInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public PowerShellProviderBase(PowerShell ps, PSModuleInfo module) {
|
||||
if (module == null) {
|
||||
throw new ArgumentNullException("module");
|
||||
}
|
||||
|
||||
_powershell = ps;
|
||||
_module = module;
|
||||
|
||||
// combine all the cmdinfos we care about
|
||||
// but normalize the keys as we go (remove any '-' '_' chars)
|
||||
foreach (var k in _module.ExportedAliases.Keys) {
|
||||
_allCommands.AddOrSet(k.Replace("-", "").Replace("_", ""), _module.ExportedAliases[k]);
|
||||
}
|
||||
foreach (var k in _module.ExportedCmdlets.Keys) {
|
||||
_allCommands.AddOrSet(k.Replace("-", "").Replace("_", ""), _module.ExportedCmdlets[k]);
|
||||
}
|
||||
foreach (var k in _module.ExportedFunctions.Keys) {
|
||||
_allCommands.AddOrSet(k.Replace("-", "").Replace("_", ""), _module.ExportedFunctions[k]);
|
||||
}
|
||||
}
|
||||
|
||||
public string ModulePath {
|
||||
get {
|
||||
return _module.Path;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
if (_powershell != null) {
|
||||
_powershell.Dispose();
|
||||
_powershell = null;
|
||||
}
|
||||
|
||||
if (_reentrancyLock != null) {
|
||||
_reentrancyLock.Dispose();
|
||||
_reentrancyLock = null;
|
||||
}
|
||||
|
||||
_module = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal CommandInfo GetMethod(string methodName) {
|
||||
return _methods.GetOrAdd(methodName, () => {
|
||||
if (_allCommands.ContainsKey(methodName)) {
|
||||
return _allCommands[methodName];
|
||||
}
|
||||
|
||||
// try simple plurals to single
|
||||
if (methodName.EndsWith("s", StringComparison.OrdinalIgnoreCase)) {
|
||||
var meth = methodName.Substring(0, methodName.Length - 1);
|
||||
if (_allCommands.ContainsKey(meth)) {
|
||||
return _allCommands[meth];
|
||||
}
|
||||
}
|
||||
|
||||
// try words like Dependencies to Dependency
|
||||
if (methodName.EndsWith("cies", StringComparison.OrdinalIgnoreCase)) {
|
||||
var meth = methodName.Substring(0, methodName.Length - 4) + "cy";
|
||||
if (_allCommands.ContainsKey(meth)) {
|
||||
return _allCommands[meth];
|
||||
}
|
||||
}
|
||||
|
||||
// try IsFoo to Test-IsFoo
|
||||
if (methodName.IndexOf("Is", StringComparison.OrdinalIgnoreCase) == 0) {
|
||||
var meth = "test" + methodName;
|
||||
if (_allCommands.ContainsKey(meth)) {
|
||||
return _allCommands[meth];
|
||||
}
|
||||
}
|
||||
|
||||
if (methodName.IndexOf("add", StringComparison.OrdinalIgnoreCase) == 0) {
|
||||
// try it with 'register' instead
|
||||
var result = GetMethod("register" + methodName.Substring(3));
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (methodName.IndexOf("remove", StringComparison.OrdinalIgnoreCase) == 0) {
|
||||
// try it with 'register' instead
|
||||
var result = GetMethod("unregister" + methodName.Substring(6));
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// can't find one, return null.
|
||||
return null;
|
||||
});
|
||||
|
||||
// hmm, it is possible to get the parameter types to match better when binding.
|
||||
// module.ExportedFunctions.FirstOrDefault().Value.Parameters.Values.First().ParameterType
|
||||
}
|
||||
|
||||
internal object CallPowerShellWithoutRequest(string method, params object[] args) {
|
||||
var cmdInfo = GetMethod(method);
|
||||
if (cmdInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = _powershell.InvokeFunction<object>(cmdInfo.Name, null, null, args);
|
||||
if (result == null) {
|
||||
// failure!
|
||||
throw new Exception(Messages.PowershellScriptFunctionReturnsNull.format(_module.Name, method));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// lock is on this instance only
|
||||
|
||||
internal void ReportErrors(PsRequest request, IEnumerable<ErrorRecord> errors) {
|
||||
foreach (var error in errors) {
|
||||
request.Error(error.FullyQualifiedErrorId, error.CategoryInfo.Category.ToString(), error.TargetObject == null ? null : error.TargetObject.ToString(), error.ErrorDetails == null ? error.Exception.Message : error.ErrorDetails.Message);
|
||||
if (!string.IsNullOrWhiteSpace(error.Exception.StackTrace)) {
|
||||
// give a debug hint if we have a script stack trace. How nice of us.
|
||||
// the exception stack trace gives better stack than the script stack trace
|
||||
request.Debug(Constants.ScriptStackTrace, error.Exception.StackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IAsyncResult _stopResult;
|
||||
private object _stopLock = new object();
|
||||
|
||||
internal void CancelRequest() {
|
||||
if (!_reentrancyLock.WaitOne(0)) {
|
||||
// it's running right now.
|
||||
#if DEBUG
|
||||
NativeMethods.OutputDebugString("[Cmdlet:debugging] -- Stopping powershell script.");
|
||||
#endif
|
||||
lock (_stopLock) {
|
||||
if (_stopResult == null) {
|
||||
_stopResult = _powershell.BeginStop(ar => { }, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object CallPowerShell(PsRequest request, params object[] args) {
|
||||
// the lock ensures that we're not re-entrant into the same powershell runspace
|
||||
lock (_lock) {
|
||||
if (!_reentrancyLock.WaitOne(0)) {
|
||||
// this lock is set to false, meaning we're still in a call **ON THIS THREAD**
|
||||
// this is bad karma -- powershell won't let us call into the runspace again
|
||||
// we're going to throw an error here because this indicates that the currently
|
||||
// running powershell call is calling back into PM, and it has called back
|
||||
// into this provider. That's just bad bad bad.
|
||||
throw new Exception("Re-entrancy Violation in powershell module");
|
||||
}
|
||||
|
||||
try {
|
||||
// otherwise, this is the first time we've been here during this call.
|
||||
_reentrancyLock.Reset();
|
||||
|
||||
_powershell.SetVariable("request", request);
|
||||
_powershell.Streams.ClearStreams();
|
||||
|
||||
object finalValue = null;
|
||||
ConcurrentBag<ErrorRecord> errors = new ConcurrentBag<ErrorRecord>();
|
||||
|
||||
request.Debug("INVOKING PowerShell Fn {0} with args {1} that has length {2}", request.CommandInfo.Name, String.Join(", ", args), args.Length);
|
||||
|
||||
var result = _powershell.InvokeFunction<object>(request.CommandInfo.Name,
|
||||
(sender, e) => output_DataAdded(sender, e, request, ref finalValue),
|
||||
(sender, e) => error_DataAdded(sender, e, request, errors),
|
||||
args);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
// result is null but it does not mean that the call fails because the command may return nothing
|
||||
request.Debug(Messages.PowershellScriptFunctionReturnsNull.format(_module.Name, request.CommandInfo.Name));
|
||||
}
|
||||
|
||||
if (errors.Count > 0)
|
||||
{
|
||||
// report the error if there are any
|
||||
ReportErrors(request, errors);
|
||||
_powershell.Streams.Error.Clear();
|
||||
}
|
||||
|
||||
return finalValue;
|
||||
} catch (CmdletInvocationException cie) {
|
||||
var error = cie.ErrorRecord;
|
||||
request.Error(error.FullyQualifiedErrorId, error.CategoryInfo.Category.ToString(), error.TargetObject == null ? null : error.TargetObject.ToString(), error.ErrorDetails == null ? error.Exception.Message : error.ErrorDetails.Message);
|
||||
} finally {
|
||||
lock (_stopLock) {
|
||||
if (_stopResult != null){
|
||||
_powershell.EndStop(_stopResult);
|
||||
_stopResult = null;
|
||||
}
|
||||
}
|
||||
_powershell.Clear();
|
||||
_powershell.SetVariable("request", null);
|
||||
// it's ok if someone else calls into this module now.
|
||||
request.Debug("Done calling powershell", request.CommandInfo.Name, _module.Name);
|
||||
_reentrancyLock.Set();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void error_DataAdded(object sender, DataAddedEventArgs e, PsRequest request, ConcurrentBag<ErrorRecord> errors)
|
||||
{
|
||||
PSDataCollection<ErrorRecord> errorStream = sender as PSDataCollection<ErrorRecord>;
|
||||
|
||||
if (errorStream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var error = errorStream[e.Index];
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
// add the error so we can report them later
|
||||
errors.Add(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void output_DataAdded(object sender, DataAddedEventArgs e, PsRequest request, ref object finalValue)
|
||||
{
|
||||
PSDataCollection<PSObject> outputstream = sender as PSDataCollection<PSObject>;
|
||||
|
||||
if (outputstream == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PSObject psObject = outputstream[e.Index];
|
||||
if (psObject != null)
|
||||
{
|
||||
var value = psObject.ImmediateBaseObject;
|
||||
var y = value as Yieldable;
|
||||
if (y != null)
|
||||
{
|
||||
// yield it to stream the result gradually
|
||||
y.YieldResult(request);
|
||||
}
|
||||
else
|
||||
{
|
||||
finalValue = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
[assembly: AssemblyTitle("Microsoft.PackageManagement.MetaProvider.PowerShell")]
|
||||
[assembly: AssemblyDescription("PackageManagement Powershell MetaProvider")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Microsoft.PackageManagement.MetaProvider.PowerShell")]
|
||||
[assembly: AssemblyCopyright("Copyright Microsoft © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.PackageManagement.Test")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
||||
[assembly: Guid("8a16b9f4-f12a-42ab-a396-3c966b896722")]
|
||||
@@ -0,0 +1,213 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
|
||||
using Microsoft.PackageManagement.Internal.Utility.Plugin;
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using Microsoft.PackageManagement.Internal.Api;
|
||||
using Microsoft.PackageManagement.Internal.Implementation;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Async;
|
||||
|
||||
using Messages = Microsoft.PackageManagement.MetaProvider.PowerShell.Resources.Messages;
|
||||
|
||||
public abstract class PsRequest : Request {
|
||||
internal CommandInfo CommandInfo;
|
||||
private PowerShellProviderBase _provider;
|
||||
|
||||
internal bool IsMethodImplemented {
|
||||
get {
|
||||
return CommandInfo != null;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> PackageSources {
|
||||
get {
|
||||
var ps = Sources;
|
||||
if (ps == null) {
|
||||
return new string[] {
|
||||
};
|
||||
}
|
||||
return ps.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal IRequest Extend(params object[] objects) {
|
||||
return objects.ConcatSingleItem(this).As<IRequest>();
|
||||
}
|
||||
|
||||
internal string GetMessageStringInternal(string messageText) {
|
||||
return Messages.ResourceManager.GetString(messageText);
|
||||
}
|
||||
|
||||
public PSCredential Credential {
|
||||
get
|
||||
{
|
||||
if (CredentialUsername != null && CredentialPassword != null) {
|
||||
return new PSCredential(CredentialUsername, CredentialPassword);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Hashtable _options;
|
||||
|
||||
public Hashtable Options {
|
||||
get {
|
||||
if (_options == null) {
|
||||
_options = new Hashtable();
|
||||
//quick and dirty, grab all four sets and merge them.
|
||||
var keys = OptionKeys ?? new string[0];
|
||||
foreach (var k in keys) {
|
||||
if (_options.ContainsKey(k)) {
|
||||
continue;
|
||||
}
|
||||
var values = GetOptionValues(k).ToArray();
|
||||
if (values.Length == 1) {
|
||||
if (values[0].IsTrue()) {
|
||||
_options.Add(k, true);
|
||||
} else if (values[0].IndexOf("SECURESTRING:", StringComparison.OrdinalIgnoreCase) == 0) {
|
||||
#if !CORECLR
|
||||
_options.Add(k, values[0].Substring(13).FromProtectedString("salt"));
|
||||
#endif
|
||||
} else {
|
||||
_options.Add(k, values[0]);
|
||||
}
|
||||
} else {
|
||||
_options.Add(k, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _options;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is required for the PowerShell Providers.")]
|
||||
public object CloneRequest(Hashtable options = null, ArrayList sources = null, PSCredential credential = null) {
|
||||
var srcs = (sources ?? new ArrayList()).ToArray().Select(each => each.ToString()).ToArray();
|
||||
|
||||
options = options ?? new Hashtable();
|
||||
|
||||
var lst = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var k in options.Keys) {
|
||||
if (k != null) {
|
||||
var obj = options[k];
|
||||
|
||||
string[] val = null;
|
||||
|
||||
if (obj is string) {
|
||||
val = new[] {obj as string};
|
||||
} else {
|
||||
// otherwise, try to cast it to a collection of string-like-things
|
||||
var collection = obj as IEnumerable;
|
||||
if (collection != null) {
|
||||
val = collection.Cast<object>().Select(each => each.ToString()).ToArray();
|
||||
} else {
|
||||
// meh. ToString, and goodnight.
|
||||
val = new[] {obj.ToString()};
|
||||
}
|
||||
}
|
||||
|
||||
lst.Add(k.ToString(), val);
|
||||
}
|
||||
}
|
||||
|
||||
return Extend(new {
|
||||
GetOptionKeys = new Func<IEnumerable<string>>(() => {return lst.Keys.ToArray();}),
|
||||
GetOptionValues = new Func<string, IEnumerable<string>>((key) => {
|
||||
if (lst.ContainsKey(key)) {
|
||||
return lst[key];
|
||||
}
|
||||
return new string[0];
|
||||
}),
|
||||
GetSources = new Func<IEnumerable<string>>(() => {return srcs;}),
|
||||
GetCredentialUsername = new Func<string>(() => {return credential != null ? credential.UserName : null;}),
|
||||
GetCredentialPassword = new Func<SecureString>(() => {return credential != null ? credential.Password: null;}),
|
||||
ShouldContinueWithUntrustedPackageSource = new Func<string, string, bool>((pkgName, pkgSource) => {
|
||||
// chained providers provide locations, and don't rely on 'trusted' flags from the upstream provider.
|
||||
return true;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public object CallPowerShell(params object[] args) {
|
||||
if (IsMethodImplemented) {
|
||||
return _provider.CallPowerShell(this, args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static PsRequest New(Object requestObject, PowerShellProviderBase provider, string methodName) {
|
||||
if (requestObject is IAsyncAction) {
|
||||
((IAsyncAction)(requestObject)).OnCancel += provider.CancelRequest;
|
||||
((IAsyncAction)(requestObject)).OnAbort += provider.CancelRequest;
|
||||
}
|
||||
var req = requestObject.As<PsRequest>();
|
||||
|
||||
req.CommandInfo = provider.GetMethod(methodName);
|
||||
if (req.CommandInfo == null) {
|
||||
req.Debug("METHOD_NOT_IMPLEMENTED", methodName);
|
||||
}
|
||||
req._provider = provider;
|
||||
|
||||
if (req.Options == null) {
|
||||
req.Debug("req.Options is null");
|
||||
|
||||
} else {
|
||||
|
||||
req.Debug("Calling New() : MethodName = '{0}'", methodName);
|
||||
|
||||
foreach(string key in req.Options.Keys)
|
||||
{
|
||||
req.Debug(String.Format(CultureInfo.CurrentCulture, "{0}: {1}", key, req.Options[key]));
|
||||
}
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
public object SelectProvider(string providerName) {
|
||||
return PackageManagementService.SelectProviders(providerName, Extend()).FirstOrDefault(each => each.Name.EqualsIgnoreCase(providerName));
|
||||
}
|
||||
|
||||
public IEnumerable<object> SelectProviders(string providerName) {
|
||||
return PackageManagementService.SelectProviders(providerName, Extend());
|
||||
}
|
||||
|
||||
|
||||
public object Services {
|
||||
get {
|
||||
return ProviderServices;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<object> FindPackageByCanonicalId(string packageId, object requestObject) {
|
||||
return ProviderServices.FindPackageByCanonicalId(packageId, (requestObject ?? new object()) .As<IRequest>());
|
||||
}
|
||||
|
||||
public bool RequirePackageProvider(string packageProviderName, string minimumVersion) {
|
||||
var pp = (_provider as PowerShellPackageProvider);
|
||||
return PackageManagementService.RequirePackageProvider(pp == null ? Constants.ProviderNameUnknown : pp.GetPackageProviderName(), packageProviderName, minimumVersion, Extend());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell {
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Internal;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
public class SoftwareIdentity : Yieldable {
|
||||
public SoftwareIdentity() {
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string fastPackageReference, string name, string version, string versionScheme, string source, string summary, string searchKey, string fullPath, string filename) {
|
||||
FastPackageReference = fastPackageReference;
|
||||
Name = name;
|
||||
Version = version;
|
||||
VersionScheme = versionScheme;
|
||||
Source = source;
|
||||
Summary = summary;
|
||||
SearchKey = searchKey;
|
||||
FullPath = fullPath;
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string fastPackageReference, string name, string version, string versionScheme, string source, string summary, string searchKey, string fullPath, string filename, Hashtable details, ArrayList entities, ArrayList links, bool fromTrustedSource)
|
||||
: this(fastPackageReference, name, version, versionScheme, source, summary, searchKey,fullPath, filename) {
|
||||
_details = details;
|
||||
_entities = entities;
|
||||
_links = links;
|
||||
FromTrustedSource = fromTrustedSource;
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string fastPackageReference, string name, string version, string versionScheme, string source, string summary, string searchKey, string fullPath, string filename, Hashtable details, ArrayList entities, ArrayList links, bool fromTrustedSource, ArrayList dependencies)
|
||||
: this(fastPackageReference, name, version, versionScheme, source, summary, searchKey, fullPath, filename) {
|
||||
_details = details;
|
||||
_entities = entities;
|
||||
_links = links;
|
||||
FromTrustedSource = fromTrustedSource;
|
||||
_dependencies = dependencies;
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string fastPackageReference, string name, string version, string versionScheme, string source, string summary, string searchKey, string fullPath, string filename, Hashtable details, ArrayList entities, ArrayList links, bool fromTrustedSource, ArrayList dependencies, string tagId, string xmlLang)
|
||||
: this(fastPackageReference, name, version, versionScheme, source, summary, searchKey, fullPath, filename)
|
||||
{
|
||||
_details = details;
|
||||
_entities = entities;
|
||||
_links = links;
|
||||
FromTrustedSource = fromTrustedSource;
|
||||
_dependencies = dependencies;
|
||||
_tagId = tagId;
|
||||
_xmlLang = xmlLang;
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string fastPackageReference, string name, string version, string versionScheme, string source, string summary, string searchKey, string fullPath, string filename, Hashtable details, ArrayList entities, ArrayList links, bool fromTrustedSource, ArrayList dependencies, string tagId, string xmlLang, string destinationPath)
|
||||
: this(fastPackageReference, name, version, versionScheme, source, summary, searchKey, fullPath, filename)
|
||||
{
|
||||
_details = details;
|
||||
_entities = entities;
|
||||
_links = links;
|
||||
FromTrustedSource = fromTrustedSource;
|
||||
_dependencies = dependencies;
|
||||
_tagId = tagId;
|
||||
_xmlLang = xmlLang;
|
||||
_destinationPath = destinationPath;
|
||||
}
|
||||
|
||||
public SoftwareIdentity(string xmlSwidTag, bool commitImmediately)
|
||||
{
|
||||
_xmlSwidTag = xmlSwidTag;
|
||||
_commitImmediately = commitImmediately;
|
||||
}
|
||||
|
||||
|
||||
public string FastPackageReference {get; set;}
|
||||
public string Name {get; set;}
|
||||
public string Version {get; set;}
|
||||
public string VersionScheme {get; set;}
|
||||
public string Source {get; set;}
|
||||
public string Summary {get; set;}
|
||||
|
||||
public string FullPath { get; set; }
|
||||
public string Filename { get; set; }
|
||||
|
||||
public string SearchKey {get; set;}
|
||||
|
||||
public bool FromTrustedSource {get; set;}
|
||||
|
||||
public override bool YieldResult(PsRequest r) {
|
||||
if (r == null) {
|
||||
throw new ArgumentNullException("r");
|
||||
}
|
||||
|
||||
// if we get an xml swidtag string, use that
|
||||
if (!string.IsNullOrWhiteSpace(_xmlSwidTag))
|
||||
{
|
||||
return r.YieldSoftwareIdentityXml(_xmlSwidTag, _commitImmediately) != null;
|
||||
}
|
||||
|
||||
bool result = r.YieldSoftwareIdentity(FastPackageReference, Name, Version, VersionScheme, Summary, Source, SearchKey, FullPath, Filename) != null && YieldTagId(r) && YieldXmlLang(r)
|
||||
&& YieldDetails(r) && YieldEntities(r) && YieldLinks(r) && YieldDependencies(r) && r.AddMetadata(FastPackageReference, "FromTrustedSource", FromTrustedSource.ToString()) != null;
|
||||
|
||||
// check whether we have destination
|
||||
if (!string.IsNullOrWhiteSpace(_destinationPath))
|
||||
{
|
||||
var payload = r.AddPayload();
|
||||
|
||||
// could not add the payload, return false
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (r.AddDirectory(payload, System.IO.Path.GetFileName(_destinationPath), System.IO.Path.GetDirectoryName(_destinationPath), null, true) == null)
|
||||
{
|
||||
// cannot add directory
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ArrayList _links;
|
||||
private ArrayList _entities;
|
||||
private ArrayList _dependencies;
|
||||
private string _destinationPath;
|
||||
private string _tagId;
|
||||
private string _xmlLang;
|
||||
private string _xmlSwidTag;
|
||||
private bool _commitImmediately;
|
||||
|
||||
protected virtual bool YieldTagId(PsRequest r)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_tagId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return r.AddTagId(_tagId) != null;
|
||||
}
|
||||
|
||||
protected virtual bool YieldXmlLang(PsRequest r)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_xmlLang))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return r.AddCulture(_xmlLang) != null;
|
||||
}
|
||||
protected override bool YieldDetails(PsRequest r) {
|
||||
if (_details != null && _details.Count > 0) {
|
||||
// we need to send this back as a set of key/path & value pairs.
|
||||
return _details.Flatten().All(kvp => r.AddMetadata(FastPackageReference,kvp.Key, kvp.Value) != null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool YieldLinks(PsRequest r) {
|
||||
if( _links != null ) {
|
||||
return _links.OfType<Link>().All(link => r.AddLink(new Uri(link.HRef), link.Relationship, link.MediaType, link.Ownership, link.Use, link.AppliesToMedia, link.Artifact) != null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual bool YieldDependencies(PsRequest r) {
|
||||
if (_dependencies != null) {
|
||||
return _dependencies.OfType<Dependency>().All(dep => r.AddDependency(dep.ProviderName, dep.PackageName, dep.Version, dep.Source, dep.AppliesTo ) != null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected virtual bool YieldEntities(PsRequest r) {
|
||||
if (_links != null) {
|
||||
return _entities.OfType<Entity>().All(entity => r.AddEntity(entity.Name, entity.RegId, entity.Role, entity.Thumbprint) != null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
namespace Microsoft.PackageManagement.MetaProvider.PowerShell.Internal {
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Microsoft.PackageManagement.Internal.Utility.Extensions;
|
||||
|
||||
public abstract class Yieldable {
|
||||
protected Hashtable _details;
|
||||
|
||||
public Hashtable Details {
|
||||
get {
|
||||
return _details ?? (_details = new Hashtable());
|
||||
}
|
||||
internal set {
|
||||
_details = value;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract bool YieldResult(PsRequest r);
|
||||
|
||||
protected virtual bool YieldDetails(PsRequest r) {
|
||||
if (_details != null && _details.Count > 0) {
|
||||
// we need to send this back as a set of key/path & value pairs.
|
||||
return _details.Flatten().All(kvp => r.YieldKeyValuePair(kvp.Key, kvp.Value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/provider.manifest": "provider.manifest",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Constants.cs": "Constants.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Dependency.cs": "Dependency.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/DynamicOption.cs": "DynamicOption.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Entity.cs": "Entity.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Enums.cs": "Enums.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Feature.cs": "Feature.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Link.cs": "Link.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/PackageSource.cs": "PackageSource.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/PowerShellExtensions.cs": "PowerShellExtensions.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/PowerShellMetaProvider.cs": "PowerShellMetaProvider.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/PowerShellPackageProvider.cs": "PowerShellPackageProvider.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/PowerShellProviderBase.cs": "PowerShellProviderBase.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Request.cs": "Request.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/SoftwareIdentity.cs": "SoftwareIdentity.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Yieldable.cs": "Yieldable.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Properties/AssemblyInfo.cs": "Properties/AssemblyInfo.cs",
|
||||
"monad/src/oneget/providers/inbox/powershell.metaprovider/Resources/Messages.resx": "resources/Microsoft.PackageManagement.MetaProvider.PowerShell.Resources.Messages.resx"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "Microsoft.PackageManagement.MetaProvider.PowerShell",
|
||||
"version": "1.0.0-*",
|
||||
"authors": ["quoct"],
|
||||
|
||||
"buildOptions": {
|
||||
"warningsAsErrors": true
|
||||
},
|
||||
|
||||
"configurations": {
|
||||
"Linux": {
|
||||
"buildOptions": {
|
||||
"define": ["LINUX"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"System.Management.Automation": "1.0.0-*",
|
||||
"Microsoft.PackageManagement": "1.0.0-*"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"frameworkAssemblies": {
|
||||
"System.Runtime": "",
|
||||
"System.Xml": "",
|
||||
"System.Xml.Linq": "",
|
||||
"System.Numerics": "",
|
||||
"System.Data": "",
|
||||
"System.DirectoryServices": "",
|
||||
"System.Security": "",
|
||||
"System.Transactions": "",
|
||||
"System.Runtime.Serialization": "",
|
||||
"System.Management": "",
|
||||
"System.Configuration": "",
|
||||
"System.Configuration.Install": "",
|
||||
"System.Net": "",
|
||||
"System.Net.Http": "",
|
||||
"System.IO.Compression": "",
|
||||
"System.IO.Compression.FileSystem": ""
|
||||
}
|
||||
},
|
||||
|
||||
"netstandard1.5": {
|
||||
"imports": ["dnxcore50"],
|
||||
"buildOptions": {
|
||||
"define": ["CORECLR"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<SoftwareIdentity
|
||||
name="PowerShell MetaProvider for PackageManagement"
|
||||
version="1.0.0.0" versionScheme="multipartnumeric"
|
||||
media="(windows) and (PackageManagement) and (min-PackageManagement.api:2) and (PowerShell) and (min-PowerShell:3.0)"
|
||||
xmlns="http://standards.iso.org/iso/19770/-2/2015/schema.xsd">
|
||||
|
||||
</SoftwareIdentity>
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="DuplicateProviderName" xml:space="preserve">
|
||||
<value>Fail to import the provider '{0}' because the provider with the same name also exists from path '{1}'.</value>
|
||||
<comment>{0} provider path {1} provider path</comment>
|
||||
</data>
|
||||
<data name="FailedToImportProvider" xml:space="preserve">
|
||||
<value>Import-PackageProvider '{0}' failed.</value>
|
||||
<comment>0-provider name or path</comment>
|
||||
</data>
|
||||
<data name="FileNotFound" xml:space="preserve">
|
||||
<value>File '{0}' not found.</value>
|
||||
<comment>{0} module path</comment>
|
||||
</data>
|
||||
<data name="ImportModuleFailed" xml:space="preserve">
|
||||
<value>Import-Module failed: '{0}'.</value>
|
||||
<comment>0- module name or path</comment>
|
||||
</data>
|
||||
<data name="ModuleAlreadyLoaded" xml:space="preserve">
|
||||
<value>The module '{0}' is already loaded.</value>
|
||||
<comment>{0} - module name</comment>
|
||||
</data>
|
||||
<data name="ModuleNotFound" xml:space="preserve">
|
||||
<value>Cannot find provider '{0}' under the specified path.</value>
|
||||
</data>
|
||||
<data name="PackageManagementProvidersNotFound" xml:space="preserve">
|
||||
<value>'PackageManagementProviders' key is not found in the .psd1 file under '{0}'.</value>
|
||||
<comment>{0} file path.</comment>
|
||||
</data>
|
||||
<data name="PowershellScriptFunctionReturnsNull" xml:space="preserve">
|
||||
<value>PowerShell Script '{0}' Function '{1}' returns null.</value>
|
||||
<comment>{0} script name, {1} function name</comment>
|
||||
</data>
|
||||
<data name="ProvideNameMismatch" xml:space="preserve">
|
||||
<value>Unable to find provider '{0}'. Please check if the ProviderName is set to '{1}' in '{2}'. </value>
|
||||
<comment>{0} provider name, {1} provider path, {2} provider name</comment>
|
||||
</data>
|
||||
<data name="ProviderNameIsNullOrEmpty" xml:space="preserve">
|
||||
<value>Name of the provider '{0}' is null, empty or not implemented by the provider.</value>
|
||||
<comment>0-provider name</comment>
|
||||
</data>
|
||||
<data name="RootModuleAndPackageManagementProviderNameNotMatch" xml:space="preserve">
|
||||
<value>Root module {0} does not match the packagemanagementprovider name {1}.</value>
|
||||
</data>
|
||||
<data name="ScriptStackTrace" xml:space="preserve">
|
||||
<value>Script failure at : {0}</value>
|
||||
<comment>{0} script location</comment>
|
||||
</data>
|
||||
<data name="SuccessfullyLoadedModule" xml:space="preserve">
|
||||
<value>Loaded PowerShell package provider: '[{0}]'.</value>
|
||||
</data>
|
||||
<data name="UnableToFindModuleProvider" xml:space="preserve">
|
||||
<value>Unable to find package provider '{0}' under the specified path.</value>
|
||||
<comment>{0} module name</comment>
|
||||
</data>
|
||||
<data name="UnableToFindPowerShellFunctionsFile" xml:space="preserve">
|
||||
<value>Unable to find 'PackageProviderFunctions.psm1' at '{0}'</value>
|
||||
</data>
|
||||
<data name="UnableToLoadModule" xml:space="preserve">
|
||||
<value>Cannot load module '{0}' because no valid provider found under the specified path.</value>
|
||||
</data>
|
||||
<data name="CantFindBasePowerShellModuleFolder" xml:space="preserve">
|
||||
<value>Cannot find base Powershell module folder.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,63 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="BitmapResource.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A subclass of Resource which provides specific methods for manipulating the resource data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resource is of type <see cref="ResourceType.Bitmap"/> (RT_GROUPICON).
|
||||
/// </remarks>
|
||||
internal sealed class BitmapResource : Resource
|
||||
{
|
||||
private const int SizeOfBitmapFileHeader = 14; // this is the sizeof(BITMAPFILEHEADER)
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new BitmapResource object without any data. The data can be later loaded from a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
public BitmapResource(string name, int locale)
|
||||
: this(name, locale, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new BitmapResource object with data. The data can be later saved to a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
/// <param name="data">Raw resource data</param>
|
||||
public BitmapResource(string name, int locale, byte[] data)
|
||||
: base(ResourceType.Bitmap, name, locale, data)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the bitmap from a .bmp file.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to a bitmap file (.bmp).</param>
|
||||
public void ReadFromFile(string path)
|
||||
{
|
||||
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
// Move past the BITMAPFILEHEADER, and copy the rest of the bitmap as the resource data. Resource
|
||||
// functions expect only the BITMAPINFO struct which exists just beyond the BITMAPFILEHEADER
|
||||
// struct in bitmap files.
|
||||
fs.Seek(BitmapResource.SizeOfBitmapFileHeader, SeekOrigin.Begin);
|
||||
|
||||
base.Data = new byte[fs.Length - BitmapResource.SizeOfBitmapFileHeader];
|
||||
fs.Read(base.Data, 0, base.Data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="FixedFileVersionInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
internal class FixedFileVersionInfo
|
||||
{
|
||||
public FixedFileVersionInfo()
|
||||
{
|
||||
// Set reasonable defaults
|
||||
this.signature = 0xFEEF04BD;
|
||||
this.structVersion = 0x00010000; // v1.0
|
||||
this.FileVersion = new Version(0, 0, 0, 0);
|
||||
this.ProductVersion = new Version(0, 0, 0, 0);
|
||||
this.FileFlagsMask = VersionBuildTypes.Debug | VersionBuildTypes.Prerelease;
|
||||
this.FileFlags = VersionBuildTypes.None;
|
||||
this.FileOS = VersionFileOS.NT_WINDOWS32;
|
||||
this.FileType = VersionFileType.Application;
|
||||
this.FileSubtype = VersionFileSubtype.Unknown;
|
||||
this.Timestamp = DateTime.MinValue;
|
||||
}
|
||||
|
||||
private uint signature;
|
||||
private uint structVersion;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public Version FileVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.fileVersion;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this.fileVersion = value;
|
||||
}
|
||||
}
|
||||
private Version fileVersion;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public Version ProductVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.productVersion;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
this.productVersion = value;
|
||||
}
|
||||
}
|
||||
private Version productVersion;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionBuildTypes FileFlagsMask
|
||||
{
|
||||
get { return this.fileFlagsMask; }
|
||||
set { this.fileFlagsMask = value; }
|
||||
}
|
||||
private VersionBuildTypes fileFlagsMask;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionBuildTypes FileFlags
|
||||
{
|
||||
get { return this.fileFlags; }
|
||||
set { this.fileFlags = value; }
|
||||
}
|
||||
private VersionBuildTypes fileFlags;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionFileOS FileOS
|
||||
{
|
||||
get { return this.fileOS; }
|
||||
set { this.fileOS = value; }
|
||||
}
|
||||
private VersionFileOS fileOS;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionFileType FileType
|
||||
{
|
||||
get { return this.fileType; }
|
||||
set { this.fileType = value; }
|
||||
}
|
||||
private VersionFileType fileType;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionFileSubtype FileSubtype
|
||||
{
|
||||
get { return this.fileSubtype; }
|
||||
set { this.fileSubtype = value; }
|
||||
}
|
||||
private VersionFileSubtype fileSubtype;
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get { return this.timestamp; }
|
||||
set { this.timestamp = value; }
|
||||
}
|
||||
private DateTime timestamp;
|
||||
|
||||
public void Read(BinaryReader reader)
|
||||
{
|
||||
this.signature = reader.ReadUInt32();
|
||||
this.structVersion = reader.ReadUInt32();
|
||||
this.fileVersion = UInt64ToVersion(reader.ReadUInt64());
|
||||
this.productVersion = UInt64ToVersion(reader.ReadUInt64());
|
||||
this.fileFlagsMask = (VersionBuildTypes) reader.ReadInt32();
|
||||
this.fileFlags = (VersionBuildTypes) reader.ReadInt32();
|
||||
this.fileOS = (VersionFileOS) reader.ReadInt32();
|
||||
this.fileType = (VersionFileType) reader.ReadInt32();
|
||||
this.fileSubtype = (VersionFileSubtype) reader.ReadInt32();
|
||||
this.timestamp = UInt64ToDateTime(reader.ReadUInt64());
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(this.signature);
|
||||
writer.Write(this.structVersion);
|
||||
writer.Write(VersionToUInt64(this.fileVersion));
|
||||
writer.Write(VersionToUInt64(this.productVersion));
|
||||
writer.Write((int) this.fileFlagsMask);
|
||||
writer.Write((int) this.fileFlags);
|
||||
writer.Write((int) this.fileOS);
|
||||
writer.Write((int) this.fileType);
|
||||
writer.Write((int) this.fileSubtype);
|
||||
writer.Write(DateTimeToUInt64(this.timestamp));
|
||||
}
|
||||
|
||||
public static explicit operator FixedFileVersionInfo(byte[] bytesValue)
|
||||
{
|
||||
FixedFileVersionInfo ffviValue = new FixedFileVersionInfo();
|
||||
using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
|
||||
{
|
||||
ffviValue.Read(reader);
|
||||
}
|
||||
return ffviValue;
|
||||
}
|
||||
|
||||
public static explicit operator byte[](FixedFileVersionInfo ffviValue)
|
||||
{
|
||||
const int FFVI_LENGTH = 52;
|
||||
|
||||
byte[] bytesValue = new byte[FFVI_LENGTH];
|
||||
using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
|
||||
{
|
||||
ffviValue.Write(writer);
|
||||
}
|
||||
return bytesValue;
|
||||
}
|
||||
|
||||
private static Version UInt64ToVersion(ulong version)
|
||||
{
|
||||
return new Version((int) ((version >> 16) & 0xFFFF), (int) (version & 0xFFFF), (int) (version >> 48), (int) ((version >> 32) & 0xFFFF));
|
||||
}
|
||||
private static ulong VersionToUInt64(Version version)
|
||||
{
|
||||
return (((ulong) (ushort) version.Major) << 16) | ((ulong) (ushort) version.Minor)
|
||||
| (((ulong) (ushort) version.Build) << 48) | (((ulong) (ushort) version.Revision) << 32);
|
||||
}
|
||||
|
||||
private static DateTime UInt64ToDateTime(ulong dateTime)
|
||||
{
|
||||
return (dateTime == 0 ? DateTime.MinValue : DateTime.FromFileTime((long) dateTime));
|
||||
}
|
||||
private static ulong DateTimeToUInt64(DateTime dateTime)
|
||||
{
|
||||
return (dateTime == DateTime.MinValue ? 0 : (ulong) dateTime.ToFileTime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="GroupIconInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
internal enum GroupIconType
|
||||
{
|
||||
Unknown,
|
||||
Icon,
|
||||
Cursor,
|
||||
}
|
||||
|
||||
internal struct GroupIconDirectoryInfo
|
||||
{
|
||||
public byte width;
|
||||
public byte height;
|
||||
public byte colors;
|
||||
public byte reserved;
|
||||
public ushort planes;
|
||||
public ushort bitsPerPixel;
|
||||
public uint imageSize;
|
||||
public uint imageOffset; // only valid when icon group is read from .ico file.
|
||||
public ushort imageIndex; // only valid when icon group is read from PE resource.
|
||||
}
|
||||
|
||||
internal class GroupIconInfo
|
||||
{
|
||||
private ushort reserved;
|
||||
private GroupIconType type;
|
||||
private GroupIconDirectoryInfo[] images;
|
||||
|
||||
public GroupIconInfo()
|
||||
{
|
||||
this.images = new GroupIconDirectoryInfo[0];
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public GroupIconDirectoryInfo[] DirectoryInfo { get { return this.images; } }
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public void ReadFromFile(Stream stream)
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
this.Read(reader, true);
|
||||
}
|
||||
|
||||
public void ReadFromResource(byte[] data)
|
||||
{
|
||||
using (BinaryReader reader = new BinaryReader(new MemoryStream(data, false)))
|
||||
{
|
||||
this.Read(reader, false);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetResourceData()
|
||||
{
|
||||
byte[] data = null;
|
||||
|
||||
using (MemoryStream stream = new MemoryStream())
|
||||
{
|
||||
BinaryWriter writer = new BinaryWriter(stream);
|
||||
writer.Write(this.reserved);
|
||||
writer.Write((ushort)this.type);
|
||||
writer.Write((ushort)this.images.Length);
|
||||
for (int i = 0; i < this.images.Length; ++i)
|
||||
{
|
||||
writer.Write(this.images[i].width);
|
||||
writer.Write(this.images[i].height);
|
||||
writer.Write(this.images[i].colors);
|
||||
writer.Write(this.images[i].reserved);
|
||||
writer.Write(this.images[i].planes);
|
||||
writer.Write(this.images[i].bitsPerPixel);
|
||||
writer.Write(this.images[i].imageSize);
|
||||
writer.Write(this.images[i].imageIndex);
|
||||
}
|
||||
|
||||
data = new byte[stream.Length];
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
stream.Read(data, 0, data.Length);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void Read(BinaryReader reader, bool readFromFile)
|
||||
{
|
||||
this.reserved = reader.ReadUInt16();
|
||||
this.type = (GroupIconType)reader.ReadUInt16();
|
||||
|
||||
int imageCount = reader.ReadUInt16();
|
||||
this.images = new GroupIconDirectoryInfo[imageCount];
|
||||
for (int i = 0; i < imageCount; ++i)
|
||||
{
|
||||
this.images[i].width = reader.ReadByte();
|
||||
this.images[i].height = reader.ReadByte();
|
||||
this.images[i].colors = reader.ReadByte();
|
||||
this.images[i].reserved = reader.ReadByte();
|
||||
this.images[i].planes = reader.ReadUInt16();
|
||||
this.images[i].bitsPerPixel = reader.ReadUInt16();
|
||||
this.images[i].imageSize = reader.ReadUInt32();
|
||||
if (readFromFile)
|
||||
{
|
||||
this.images[i].imageOffset = reader.ReadUInt32();
|
||||
this.images[i].imageIndex = (ushort)(i + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.images[i].imageIndex = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="GroupIconResource.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A subclass of Resource which provides specific methods for manipulating the resource data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resource is of type <see cref="ResourceType.GroupIcon"/> (RT_GROUPICON).
|
||||
/// </remarks>
|
||||
internal sealed class GroupIconResource : Resource
|
||||
{
|
||||
internal bool dirty;
|
||||
private GroupIconInfo rawGroupIconInfo;
|
||||
private List<Resource> icons;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GroupIconResource object without any data. The data can be later loaded from a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
public GroupIconResource(string name, int locale)
|
||||
: this(name, locale, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GroupIconResource object with data. The data can be later saved to a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
/// <param name="data">Raw resource data</param>
|
||||
public GroupIconResource(string name, int locale, byte[] data)
|
||||
: base(ResourceType.GroupIcon, name, locale, data)
|
||||
{
|
||||
this.RefreshIconGroupInfo(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw data of the resource. The data is in the format of the RT_GROUPICON resource structure.
|
||||
/// </summary>
|
||||
public override byte[] Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.dirty)
|
||||
{
|
||||
base.Data = this.rawGroupIconInfo.GetResourceData();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
return base.Data;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.RefreshIconGroupInfo(value);
|
||||
|
||||
base.Data = value;
|
||||
this.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the the icons in the icon group.
|
||||
/// </summary>
|
||||
public IEnumerable<Resource> Icons { get { return this.icons; } }
|
||||
|
||||
/// <summary>
|
||||
/// Reads the icon group from a .ico file.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to an icon file (.ico).</param>
|
||||
public void ReadFromFile(string path)
|
||||
{
|
||||
this.rawGroupIconInfo = new GroupIconInfo();
|
||||
this.icons = new List<Resource>();
|
||||
using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
this.rawGroupIconInfo.ReadFromFile(fs);
|
||||
|
||||
// After reading the group icon info header from the file, read all the icons.
|
||||
for (int i = 0; i < this.rawGroupIconInfo.DirectoryInfo.Length; ++i)
|
||||
{
|
||||
ushort index = this.rawGroupIconInfo.DirectoryInfo[i].imageIndex;
|
||||
uint offset = this.rawGroupIconInfo.DirectoryInfo[i].imageOffset;
|
||||
uint size = this.rawGroupIconInfo.DirectoryInfo[i].imageSize;
|
||||
byte[] data = new byte[size];
|
||||
|
||||
fs.Seek(offset, SeekOrigin.Begin);
|
||||
fs.Read(data, 0, data.Length);
|
||||
|
||||
Resource resource = new Resource(ResourceType.Icon, String.Concat("#", index), this.Locale, data);
|
||||
this.icons.Add(resource);
|
||||
}
|
||||
}
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
private void RefreshIconGroupInfo(byte[] refreshData)
|
||||
{
|
||||
this.rawGroupIconInfo = new GroupIconInfo();
|
||||
this.icons = new List<Resource>();
|
||||
if (refreshData != null)
|
||||
{
|
||||
this.rawGroupIconInfo.ReadFromResource(refreshData);
|
||||
}
|
||||
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="NativeMethods.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, uint flags);
|
||||
internal const uint LOAD_LIBRARY_AS_DATAFILE = 2;
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool FreeLibrary(IntPtr module);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumResourceTypes(IntPtr module, EnumResTypesProc enumFunc, IntPtr param);
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal delegate bool EnumResTypesProc(IntPtr module, IntPtr type, IntPtr param);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumResourceNames(IntPtr module, IntPtr type, EnumResNamesProc enumFunc, IntPtr param);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumResourceNames(IntPtr module, string type, EnumResNamesProc enumFunc, IntPtr param);
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal delegate bool EnumResNamesProc(IntPtr module, IntPtr type, IntPtr name, IntPtr param);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumResourceLanguages(IntPtr module, IntPtr type, IntPtr name, EnumResLangsProc enumFunc, IntPtr param);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EnumResourceLanguages(IntPtr module, string type, string name, EnumResLangsProc enumFunc, IntPtr param);
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal delegate bool EnumResLangsProc(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr FindResourceEx(IntPtr module, string type, string name, ushort langId);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LoadResource(IntPtr module, IntPtr resourceInfo);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr LockResource(IntPtr resourceData);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern uint SizeofResource(IntPtr module, IntPtr resourceInfo);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
internal static extern IntPtr BeginUpdateResource(string fileName, [MarshalAs(UnmanagedType.Bool)] bool deleteExistingResources);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, IntPtr name, ushort lcid, IntPtr data, uint dataSize);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UpdateResource(IntPtr updateHandle, IntPtr type, string name, ushort lcid, IntPtr data, uint dataSize);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool UpdateResource(IntPtr updateHandle, string type, string name, ushort lcid, IntPtr data, uint dataSize);
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static extern bool EndUpdateResource(IntPtr updateHandle, [MarshalAs(UnmanagedType.Bool)] bool discardChanges);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="Resource.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Permissions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Win32 resource which can be loaded from and saved to a PE file.
|
||||
/// </summary>
|
||||
internal class Resource
|
||||
{
|
||||
private ResourceType type;
|
||||
private string name;
|
||||
private int locale;
|
||||
private byte[] data;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Resource object without any data. The data can be later loaded from a file.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
public Resource(ResourceType type, string name, int locale)
|
||||
: this(type, name, locale, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Resource object with data. The data can be later saved to a file.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the resource; may be one of the ResourceType constants or a user-defined type.</param>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
/// <param name="data">Raw resource data</param>
|
||||
public Resource(ResourceType type, string name, int locale, byte[] data)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.locale = locale;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the resource. This may be one of the ResourceType constants
|
||||
/// or a user-defined type name.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public ResourceType ResourceType
|
||||
{
|
||||
get { return this.type; }
|
||||
set { this.type = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the resource. For a numeric resource identifier, the decimal number is prefixed with a "#".
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
this.name = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the locale of the resource.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public int Locale
|
||||
{
|
||||
get { return this.locale; }
|
||||
set { this.locale = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw data of the resource.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public virtual byte[] Data
|
||||
{
|
||||
get { return this.data; }
|
||||
set { this.data = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the resource data from a file. The file is searched for a resource with matching type, name, and locale.
|
||||
/// </summary>
|
||||
/// <param name="file">Win32 PE file containing the resource</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public void Load(string file)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.Read, file).Demand();
|
||||
|
||||
IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
|
||||
try
|
||||
{
|
||||
this.Load(module);
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal void Load(IntPtr module)
|
||||
{
|
||||
IntPtr resourceInfo = NativeMethods.FindResourceEx(module, (string) this.ResourceType, this.Name, (ushort) this.Locale);
|
||||
if (resourceInfo != IntPtr.Zero)
|
||||
{
|
||||
uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo);
|
||||
IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo);
|
||||
IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
|
||||
byte[] resourceBytes = new byte[resourceLength];
|
||||
Marshal.Copy(resourcePtr, resourceBytes, 0, resourceBytes.Length);
|
||||
this.Data = resourceBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Data = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the resource to a file. Any existing resource data with matching type, name, and locale is overwritten.
|
||||
/// </summary>
|
||||
/// <param name="file">Win32 PE file to contain the resource</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public void Save(string file)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.AllAccess, file).Demand();
|
||||
|
||||
IntPtr updateHandle = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
updateHandle = NativeMethods.BeginUpdateResource(file, false);
|
||||
this.Save(updateHandle);
|
||||
if (!NativeMethods.EndUpdateResource(updateHandle, false))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error code: {0}", err));
|
||||
}
|
||||
updateHandle = IntPtr.Zero;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (updateHandle != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.EndUpdateResource(updateHandle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal void Save(IntPtr updateHandle)
|
||||
{
|
||||
IntPtr dataPtr = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
int dataLength = 0;
|
||||
if (this.Data != null)
|
||||
{
|
||||
dataLength = this.Data.Length;
|
||||
dataPtr = Marshal.AllocHGlobal(dataLength);
|
||||
Marshal.Copy(this.Data, 0, dataPtr, dataLength);
|
||||
}
|
||||
bool updateSuccess;
|
||||
if (this.Name.StartsWith("#", StringComparison.Ordinal))
|
||||
{
|
||||
// A numeric-named resource must be saved via the integer version of UpdateResource.
|
||||
IntPtr intName = new IntPtr(Int32.Parse(this.Name.Substring(1), CultureInfo.InvariantCulture));
|
||||
updateSuccess = NativeMethods.UpdateResource(updateHandle, new IntPtr(this.ResourceType.IntegerValue), intName, (ushort) this.Locale, dataPtr, (uint) dataLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateSuccess = NativeMethods.UpdateResource(updateHandle, (string) this.ResourceType, this.Name, (ushort) this.Locale, dataPtr, (uint) dataLength);
|
||||
}
|
||||
if (!updateSuccess)
|
||||
{
|
||||
throw new IOException("Failed to save resource. Error: " + Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (dataPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(dataPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if type, name, and locale of this Resource object match another Resource object.
|
||||
/// </summary>
|
||||
/// <param name="obj">Resource object to be compared</param>
|
||||
/// <returns>True if the objects represent the same resource; false otherwise.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
Resource res = obj as Resource;
|
||||
if (res == null) return false;
|
||||
return this.ResourceType == res.ResourceType && this.Name == res.Name && this.Locale == res.Locale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash code for this Resource object.
|
||||
/// </summary>
|
||||
/// <returns>Hash code generated from the resource type, name, and locale.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.ResourceType.GetHashCode() ^ this.Name.GetHashCode() ^ this.Locale.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ResourceCollection.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Permissions;
|
||||
|
||||
/// <summary>
|
||||
/// Allows reading and editing of resource data in a Win32 PE file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To use this class:<list type="number">
|
||||
/// <item>Create a new ResourceCollection</item>
|
||||
/// <item>Locate resources for the collection by calling one of the <see cref="ResourceCollection.Find(string)"/> methods</item>
|
||||
/// <item>Load data of one or more <see cref="Resource"/>s from a file by calling the <see cref="Load"/> method of the
|
||||
/// Resource class, or load them all at once (more efficient) with the <see cref="Load"/> method of the ResourceCollection.</item>
|
||||
/// <item>Read and/or edit data of the individual Resource objects using the methods on that class.</item>
|
||||
/// <item>Save data of one or more <see cref="Resource"/>s to a file by calling the <see cref="Save"/> method of the
|
||||
/// Resource class, or save them all at once (more efficient) with the <see cref="Save"/> method of the ResourceCollection.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
internal class ResourceCollection : ICollection<Resource>
|
||||
{
|
||||
private List<Resource> resources;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new, empty ResourceCollection.
|
||||
/// </summary>
|
||||
public ResourceCollection()
|
||||
{
|
||||
this.resources = new List<Resource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates all resources in a file, including all resource types and languages. For each located resource,
|
||||
/// a <see cref="Resource"/> instance (or subclass) is added to the collection.
|
||||
/// </summary>
|
||||
/// <param name="resFile">The file to be searched for resources.</param>
|
||||
/// <exception cref="IOException">resources could not be read from the file</exception>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
public void Find(string resFile)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.Read, resFile).Demand();
|
||||
|
||||
this.Clear();
|
||||
|
||||
IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
|
||||
if (module == IntPtr.Zero)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to load resource file. Error code: {0}", err));
|
||||
}
|
||||
try
|
||||
{
|
||||
if (!NativeMethods.EnumResourceTypes(module, new NativeMethods.EnumResTypesProc(this.EnumResTypes), IntPtr.Zero))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to enumerate resources. Error code: {0}", err));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates all resources in a file of a given type, including all languages. For each located resource,
|
||||
/// a <see cref="Resource"/> instance (or subclass) is added to the collection.
|
||||
/// </summary>
|
||||
/// <param name="resFile">The file to be searched for resources.</param>
|
||||
/// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
|
||||
/// <exception cref="IOException">resources could not be read from the file</exception>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
public void Find(string resFile, ResourceType type)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.Read, resFile).Demand();
|
||||
|
||||
this.Clear();
|
||||
|
||||
IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
|
||||
try
|
||||
{
|
||||
if (!NativeMethods.EnumResourceNames(module, (string) type, new NativeMethods.EnumResNamesProc(this.EnumResNames), IntPtr.Zero))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error. Error code: {0}", err));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Locates all resources in a file of a given type and language. For each located resource,
|
||||
/// a <see cref="Resource"/> instance (or subclass) is added to the collection.
|
||||
/// </summary>
|
||||
/// <param name="resFile">The file to be searched for resources.</param>
|
||||
/// <param name="type">The type of resource to search for; may be one of the ResourceType constants or a user-defined type.</param>
|
||||
/// <param name="name">The name of the resource to search for.</param>
|
||||
/// <exception cref="IOException">resources could not be read from the file</exception>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
public void Find(string resFile, ResourceType type, string name)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.Read, resFile).Demand();
|
||||
|
||||
this.Clear();
|
||||
|
||||
IntPtr module = NativeMethods.LoadLibraryEx(resFile, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
|
||||
try
|
||||
{
|
||||
if (!NativeMethods.EnumResourceLanguages(module, (string) type, name, new NativeMethods.EnumResLangsProc(this.EnumResLangs), IntPtr.Zero))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnumResTypes(IntPtr module, IntPtr type, IntPtr param)
|
||||
{
|
||||
if (!NativeMethods.EnumResourceNames(module, type, new NativeMethods.EnumResNamesProc(EnumResNames), IntPtr.Zero))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceNames error! Error code: {0}", err));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnumResNames(IntPtr module, IntPtr type, IntPtr name, IntPtr param)
|
||||
{
|
||||
if (!NativeMethods.EnumResourceLanguages(module, type, name, new NativeMethods.EnumResLangsProc(EnumResLangs), IntPtr.Zero))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "EnumResourceLanguages error. Error code: {0}", err));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EnumResLangs(IntPtr module, IntPtr type, IntPtr name, ushort langId, IntPtr param)
|
||||
{
|
||||
Resource res;
|
||||
if (((int) type) == ResourceType.Version.IntegerValue)
|
||||
{
|
||||
res = new VersionResource(ResourceNameToString(name), langId);
|
||||
}
|
||||
else
|
||||
{
|
||||
res = new Resource(ResourceNameToString(type), ResourceNameToString(name), langId);
|
||||
}
|
||||
|
||||
if (!this.Contains(res))
|
||||
{
|
||||
this.Add(res);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ResourceNameToString(IntPtr resName)
|
||||
{
|
||||
if ((resName.ToInt64() >> 16) == 0)
|
||||
{
|
||||
return "#" + resName.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Marshal.PtrToStringAuto(resName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For all resources in the collection, loads their data from a resource file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file from which resources are loaded.</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
public void Load(string file)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.Read, file).Demand();
|
||||
|
||||
IntPtr module = NativeMethods.LoadLibraryEx(file, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
|
||||
try
|
||||
{
|
||||
foreach (Resource res in this)
|
||||
{
|
||||
res.Load(module);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMethods.FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For all resources in the collection, saves their data to a resource file.
|
||||
/// </summary>
|
||||
/// <param name="file">The file to which resources are saved.</param>
|
||||
[SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")]
|
||||
[SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
|
||||
public void Save(string file)
|
||||
{
|
||||
new FileIOPermission(FileIOPermissionAccess.AllAccess, file).Demand();
|
||||
|
||||
IntPtr updateHandle = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
updateHandle = NativeMethods.BeginUpdateResource(file, false);
|
||||
foreach (Resource res in this)
|
||||
{
|
||||
res.Save(updateHandle);
|
||||
}
|
||||
if (!NativeMethods.EndUpdateResource(updateHandle, false))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
throw new IOException(String.Format(CultureInfo.InvariantCulture, "Failed to save resource. Error {0}", err));
|
||||
}
|
||||
updateHandle = IntPtr.Zero;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (updateHandle != IntPtr.Zero)
|
||||
{
|
||||
NativeMethods.EndUpdateResource(updateHandle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element at the specified index.
|
||||
/// </summary>
|
||||
public Resource this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Resource) this.resources[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
this.resources[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new item to the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The Resource to add.</param>
|
||||
public void Add(Resource item)
|
||||
{
|
||||
this.resources.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item to the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The Resource to remove.</param>
|
||||
public bool Remove(Resource item)
|
||||
{
|
||||
return this.resources.Remove(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of an item in the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The Resource to search for.</param>
|
||||
/// <returns>The index of the item, or -1 if not found.</returns>
|
||||
public int IndexOf(Resource item)
|
||||
{
|
||||
return this.resources.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a item into the collection.
|
||||
/// </summary>
|
||||
/// <param name="index">The insertion index.</param>
|
||||
/// <param name="item">The Resource to insert.</param>
|
||||
public void Insert(int index, Resource item)
|
||||
{
|
||||
this.resources.Insert(index, item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if the collection contains an item.
|
||||
/// </summary>
|
||||
/// <param name="item">The Resource to search for.</param>
|
||||
/// <returns>true if the item is found; false otherwise</returns>
|
||||
public bool Contains(Resource item)
|
||||
{
|
||||
return this.resources.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the collection into an array.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to copy into.</param>
|
||||
/// <param name="arrayIndex">The starting index in the destination array.</param>
|
||||
public void CopyTo(Resource[] array, int arrayIndex)
|
||||
{
|
||||
this.resources.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all resources from the collection.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
this.resources.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of resources in the collection.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.resources.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator over all resources in the collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<Resource> GetEnumerator()
|
||||
{
|
||||
return this.resources.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable) this.resources).GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<Resource>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ResourceType.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Represents either a standard integer resource type or a custom resource type name.
|
||||
/// </summary>
|
||||
internal class ResourceType
|
||||
{
|
||||
// Silence warnings about doc-comments
|
||||
#pragma warning disable 1591
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType None { get { return "#0"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Cursor { get { return "#1"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Bitmap { get { return "#2"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Icon { get { return "#3"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Menu { get { return "#4"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Dialog { get { return "#5"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType String { get { return "#6"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType FontDir { get { return "#7"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Font { get { return "#8"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Accelerator { get { return "#9"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType RCData { get { return "#10"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType MessageTable { get { return "#11"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType GroupCursor { get { return "#12"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType GroupIcon { get { return "#14"; } }
|
||||
public static ResourceType Version { get { return "#16"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType DialogInclude { get { return "#17"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType PlugPlay { get { return "#19"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Vxd { get { return "#20"; } }
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType AniCursor { get { return "#21"; } }
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ani")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType AniIcon { get { return "#22"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Html { get { return "#23"; } }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static ResourceType Manifest { get { return "#24"; } }
|
||||
|
||||
#pragma warning restore 1591
|
||||
|
||||
private string resourceType;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new resource type from a string resource name.
|
||||
/// </summary>
|
||||
/// <param name="resourceType">String resource name,
|
||||
/// or an integer resource type prefixed by a #.</param>
|
||||
public ResourceType(string resourceType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(resourceType))
|
||||
{
|
||||
throw new ArgumentNullException("resourceType");
|
||||
}
|
||||
|
||||
this.resourceType = resourceType;
|
||||
|
||||
if (this.IsInteger && this.IntegerValue < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("resourceType");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new integer resource type.
|
||||
/// </summary>
|
||||
/// <param name="resourceType">Integer value of a well-known resource type.</param>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public ResourceType(int resourceType)
|
||||
: this("#" + resourceType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag indicating whether the resource type is an integer type.
|
||||
/// </summary>
|
||||
public bool IsInteger
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.resourceType.StartsWith("#", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the integer value of the resource type, or -1 if the resource type is not an integer.
|
||||
/// </summary>
|
||||
public int IntegerValue
|
||||
{
|
||||
get
|
||||
{
|
||||
int value;
|
||||
if (!this.IsInteger ||
|
||||
!Int32.TryParse(this.resourceType.Substring(1), out value))
|
||||
{
|
||||
value = -1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string representation of the resource type.
|
||||
/// </summary>
|
||||
/// <returns>The custom resource name, or the name of a well-known resource type.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.IsInteger)
|
||||
{
|
||||
switch (this.IntegerValue)
|
||||
{
|
||||
case 0: return "None";
|
||||
case 1: return "Cursor";
|
||||
case 2: return "Bitmap";
|
||||
case 3: return "Icon";
|
||||
case 4: return "Menu";
|
||||
case 5: return "Dialog";
|
||||
case 6: return "String";
|
||||
case 7: return "FontDir";
|
||||
case 8: return "Font";
|
||||
case 9: return "Accelerator";
|
||||
case 10: return "RCData";
|
||||
case 11: return "MessageTable";
|
||||
case 12: return "GroupCursor";
|
||||
case 14: return "GroupIcon";
|
||||
case 16: return "Version";
|
||||
case 17: return "DialogInclude";
|
||||
case 19: return "PlugPlay";
|
||||
case 20: return "Vxd";
|
||||
case 21: return "AniCursor";
|
||||
case 22: return "AniIcon";
|
||||
case 23: return "Html";
|
||||
case 24: return "Manifest";
|
||||
}
|
||||
}
|
||||
|
||||
return this.resourceType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether one resource type equals another object.
|
||||
/// </summary>
|
||||
/// <param name="obj">Other object.</param>
|
||||
/// <returns>True if equal, else false.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals(obj as ResourceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether one resource type equals another.
|
||||
/// </summary>
|
||||
/// <param name="otherType">Other resource type.</param>
|
||||
/// <returns>True if equal, else false.</returns>
|
||||
public bool Equals(ResourceType otherType)
|
||||
{
|
||||
return otherType != null && this.resourceType.Equals(otherType.resourceType, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash code suitable for using the resource type as a dictionary key.
|
||||
/// </summary>
|
||||
/// <returns>Hash code based on the resource type string.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.resourceType.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a string to a ResourceType.
|
||||
/// </summary>
|
||||
/// <param name="resourceType">String resource type to convert.</param>
|
||||
/// <returns>ResourceType object.</returns>
|
||||
public static implicit operator ResourceType(string resourceType)
|
||||
{
|
||||
return new ResourceType(resourceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly converts a ResourceType to a string.
|
||||
/// </summary>
|
||||
/// <param name="resourceType">ResourceType object to convert.</param>
|
||||
/// <returns>The resource type string.</returns>
|
||||
/// <remarks>
|
||||
/// Unlike <see cref="ToString" />, this conversion does not return
|
||||
/// the common name of well-known integer resource types. Therefore,
|
||||
/// the returned string is suitable for passing directly to Win32
|
||||
/// resource APIs that accept resource type strings.
|
||||
/// </remarks>
|
||||
public static explicit operator string(ResourceType resourceType)
|
||||
{
|
||||
return resourceType.resourceType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="VersionEnums.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
// Silence warnings about doc-comments
|
||||
#pragma warning disable 1591
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies build types of a versioned file.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum VersionBuildTypes : int
|
||||
{
|
||||
None = 0x00,
|
||||
Debug = 0x01,
|
||||
Prerelease = 0x02,
|
||||
Patched = 0x04,
|
||||
PrivateBuild = 0x08,
|
||||
InfoInferred = 0x10,
|
||||
SpecialBuild = 0x20,
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
|
||||
internal enum VersionFileOS : int
|
||||
{
|
||||
Unknown = 0x00000000,
|
||||
DOS = 0x00010000,
|
||||
OS216 = 0x00020000,
|
||||
OS232 = 0x00030000,
|
||||
NT = 0x00040000,
|
||||
WINCE = 0x00050000,
|
||||
WINDOWS16 = 0x00000001,
|
||||
PM16 = 0x00000002,
|
||||
PM32 = 0x00000003,
|
||||
WINDOWS32 = 0x00000004,
|
||||
DOS_WINDOWS16 = 0x00010001,
|
||||
DOS_WINDOWS32 = 0x00010004,
|
||||
OS216_PM16 = 0x00020002,
|
||||
OS232_PM32 = 0x00030003,
|
||||
NT_WINDOWS32 = 0x00040004,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the type of a versioned file.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
|
||||
public enum VersionFileType : int
|
||||
{
|
||||
Unknown = 0,
|
||||
Application = 1,
|
||||
Dll = 2,
|
||||
Driver = 3,
|
||||
Font = 4,
|
||||
VirtualDevice = 5,
|
||||
StaticLibrary = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the sub-type of a versioned file.
|
||||
/// </summary>
|
||||
public enum VersionFileSubtype : int
|
||||
{
|
||||
Unknown = 0,
|
||||
PrinterDriver = 1,
|
||||
KeyboardDriver = 2,
|
||||
LanguageDriver = 3,
|
||||
DisplayDriver = 4,
|
||||
MouseDriver = 5,
|
||||
NetworkDriver = 6,
|
||||
SystemDriver = 7,
|
||||
InstallableDriver = 8,
|
||||
SoundDriver = 9,
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Comm")]
|
||||
CommDriver = 10,
|
||||
InputMethodDriver = 11,
|
||||
VersionedPrinterDriver = 12,
|
||||
RasterFont = 1,
|
||||
VectorFont = 2,
|
||||
TrueTypeFont = 3,
|
||||
}
|
||||
|
||||
#pragma warning restore 1591
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="VersionInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
internal class VersionInfo : ICollection<VersionInfo>
|
||||
{
|
||||
private string key;
|
||||
private bool isString;
|
||||
private byte[] data;
|
||||
private List<VersionInfo> children;
|
||||
|
||||
public VersionInfo(string key)
|
||||
: base()
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException("key");
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.children = new List<VersionInfo>();
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.key;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException("value");
|
||||
}
|
||||
|
||||
this.key = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsString
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.isString;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.isString = value;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Data
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.data = value;
|
||||
this.isString = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(BinaryReader reader)
|
||||
{
|
||||
long basePosition = reader.BaseStream.Position;
|
||||
int verInfoSize = (int) reader.ReadUInt16();
|
||||
int valueSize = (int) reader.ReadUInt16();
|
||||
bool dataIsString = (reader.ReadUInt16() != 0);
|
||||
StringBuilder keyStringBuilder = new StringBuilder();
|
||||
char c;
|
||||
while ((c = (char) reader.ReadUInt16()) != 0)
|
||||
{
|
||||
keyStringBuilder.Append(c);
|
||||
}
|
||||
this.Key = keyStringBuilder.ToString();
|
||||
Pad(reader, basePosition);
|
||||
if (valueSize == 0)
|
||||
{
|
||||
this.data = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dataIsString) valueSize *= 2; // Count is # of chars instead of bytes
|
||||
this.data = reader.ReadBytes(valueSize);
|
||||
this.isString = dataIsString;
|
||||
Pad(reader, basePosition);
|
||||
}
|
||||
|
||||
while (reader.BaseStream.Position - basePosition < verInfoSize)
|
||||
{
|
||||
Pad(reader, basePosition);
|
||||
VersionInfo childVerInfo = new VersionInfo("");
|
||||
childVerInfo.Read(reader);
|
||||
this.children.Add(childVerInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
long basePosition = writer.BaseStream.Position;
|
||||
writer.Write((ushort) this.Length);
|
||||
byte[] valueBytes = this.data;
|
||||
writer.Write((ushort) ((valueBytes != null ? valueBytes.Length : 0) / (this.IsString ? 2 : 1)));
|
||||
writer.Write((ushort) (this.IsString ? 1 : 0));
|
||||
byte[] keyBytes = new byte[Encoding.Unicode.GetByteCount(this.Key) + 2];
|
||||
Encoding.Unicode.GetBytes(this.Key, 0, this.Key.Length, keyBytes, 0);
|
||||
writer.Write(keyBytes);
|
||||
Pad(writer, basePosition);
|
||||
if (valueBytes != null)
|
||||
{
|
||||
writer.Write(valueBytes);
|
||||
Pad(writer, basePosition);
|
||||
}
|
||||
|
||||
foreach (VersionInfo childVersionInfo in this.children)
|
||||
{
|
||||
Pad(writer, basePosition);
|
||||
childVersionInfo.Write(writer);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Pad(BinaryReader reader, long basePosition)
|
||||
{
|
||||
long position = reader.BaseStream.Position;
|
||||
int diff = (int) (position - basePosition) % 4;
|
||||
if (diff > 0) while (diff++ < 4 && reader.BaseStream.Position < reader.BaseStream.Length) reader.ReadByte();
|
||||
}
|
||||
private static void Pad(BinaryWriter writer, long basePosition)
|
||||
{
|
||||
long position = writer.BaseStream.Position;
|
||||
int diff = (int) (position - basePosition) % 4;
|
||||
if (diff > 0) while (diff++ < 4) writer.Write((byte) 0);
|
||||
}
|
||||
|
||||
private int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
int len = 6 + Encoding.Unicode.GetByteCount(this.Key) + 2;
|
||||
if (len % 4 > 0) len += (4 - len % 4);
|
||||
len += (this.data != null ? this.data.Length : 0);
|
||||
if (len % 4 > 0) len += (4 - len % 4);
|
||||
foreach (VersionInfo childVersionInfo in this.children)
|
||||
{
|
||||
if (len % 4 > 0) len += (4 - len % 4);
|
||||
len += childVersionInfo.Length;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
public static explicit operator VersionInfo(byte[] bytesValue)
|
||||
{
|
||||
VersionInfo viValue = new VersionInfo("");
|
||||
using (BinaryReader reader = new BinaryReader(new MemoryStream(bytesValue, false)))
|
||||
{
|
||||
viValue.Read(reader);
|
||||
}
|
||||
return viValue;
|
||||
}
|
||||
|
||||
public static explicit operator byte[](VersionInfo viValue)
|
||||
{
|
||||
byte[] bytesValue = new byte[viValue.Length];
|
||||
using (BinaryWriter writer = new BinaryWriter(new MemoryStream(bytesValue, true)))
|
||||
{
|
||||
viValue.Write(writer);
|
||||
}
|
||||
return bytesValue;
|
||||
}
|
||||
|
||||
public VersionInfo this[string itemKey]
|
||||
{
|
||||
get
|
||||
{
|
||||
int index = this.IndexOf(itemKey);
|
||||
if (index < 0) return null;
|
||||
return this.children[index];
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(VersionInfo item)
|
||||
{
|
||||
this.children.Add(item);
|
||||
}
|
||||
|
||||
public bool Remove(VersionInfo item)
|
||||
{
|
||||
return this.children.Remove(item);
|
||||
}
|
||||
|
||||
public bool Remove(string itemKey)
|
||||
{
|
||||
int index = this.IndexOf(itemKey);
|
||||
if (index >= 0)
|
||||
{
|
||||
this.children.RemoveAt(index);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int IndexOf(string itemKey)
|
||||
{
|
||||
for (int i = 0; i < this.children.Count; i++)
|
||||
{
|
||||
if (this.children[i].Key == itemKey) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public bool Contains(VersionInfo item)
|
||||
{
|
||||
return this.children.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(VersionInfo[] array, int index)
|
||||
{
|
||||
this.children.CopyTo(array, index);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.children.Clear();
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.children.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<VersionInfo> GetEnumerator()
|
||||
{
|
||||
return this.children.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="VersionResource.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// A subclass of Resource which provides specific methods for manipulating the resource data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resource is of type <see cref="ResourceType.Version"/> (RT_VERSION).
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
|
||||
internal sealed class VersionResource : Resource, ICollection<VersionStringTable>
|
||||
{
|
||||
internal bool dirty;
|
||||
private VersionInfo rawVersionInfo;
|
||||
private FixedFileVersionInfo rawFileVersionInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new VersionResource object without any data. The data can be later loaded from a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
public VersionResource(string name, int locale)
|
||||
: this(name, locale, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new VersionResource object with data. The data can be later saved to a file.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the resource. For a numeric resource identifier, prefix the decimal number with a "#".</param>
|
||||
/// <param name="locale">Locale of the resource</param>
|
||||
/// <param name="data">Raw resource data</param>
|
||||
public VersionResource(string name, int locale, byte[] data)
|
||||
: base(ResourceType.Version, name, locale, data)
|
||||
{
|
||||
this.RefreshVersionInfo(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw data of the resource. The data is in the format of the VS_VERSIONINFO structure.
|
||||
/// </summary>
|
||||
public override byte[] Data
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.dirty)
|
||||
{
|
||||
this.rawVersionInfo.Data = (byte[]) this.rawFileVersionInfo;
|
||||
base.Data = (byte[]) this.rawVersionInfo;
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
return base.Data;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Data = value;
|
||||
this.RefreshVersionInfo(value);
|
||||
this.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshVersionInfo(byte[] refreshData)
|
||||
{
|
||||
if (refreshData == null)
|
||||
{
|
||||
this.rawVersionInfo = new VersionInfo("VS_VERSION_INFO");
|
||||
this.rawFileVersionInfo = new FixedFileVersionInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.rawVersionInfo = (VersionInfo) refreshData;
|
||||
this.rawFileVersionInfo = (FixedFileVersionInfo) this.rawVersionInfo.Data;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binary locale-independent file version of the version resource.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public Version FileVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.FileVersion;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.FileVersion = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binary locale-independent product version of the version resource.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public Version ProductVersion
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.ProductVersion;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.ProductVersion = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a bitmask that specifies the build types of the file.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionBuildTypes BuildTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.FileFlags &
|
||||
this.rawFileVersionInfo.FileFlagsMask;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.FileFlags = value;
|
||||
this.rawFileVersionInfo.FileFlagsMask = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the general type of the file.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionFileType FileType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.FileType;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.FileType = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the specific type of the file.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionFileSubtype FileSubtype
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.FileSubtype;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.FileSubtype = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the binary creation date and time.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawFileVersionInfo.Timestamp;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.rawFileVersionInfo.Timestamp = value;
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string table for a specific locale, or null if there is no table for that locale.
|
||||
/// </summary>
|
||||
/// <seealso cref="Add(int)"/>
|
||||
/// <seealso cref="Remove(int)"/>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionStringTable this[int locale]
|
||||
{
|
||||
get
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi != null)
|
||||
{
|
||||
foreach (VersionInfo strings in svi)
|
||||
{
|
||||
int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
if (stringsLocale == locale)
|
||||
{
|
||||
return new VersionStringTable(this, strings);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new version string table for a locale.
|
||||
/// </summary>
|
||||
/// <param name="locale">Locale of the table</param>
|
||||
/// <returns>The new string table, or the existing table if the locale already existed.</returns>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public VersionStringTable Add(int locale)
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi == null)
|
||||
{
|
||||
svi = new VersionInfo("StringFileInfo");
|
||||
this.rawVersionInfo.Add(svi);
|
||||
}
|
||||
foreach (VersionInfo strings in svi)
|
||||
{
|
||||
int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
if (stringsLocale == locale)
|
||||
{
|
||||
return new VersionStringTable(this, strings);
|
||||
}
|
||||
}
|
||||
|
||||
VersionInfo newStrings = new VersionInfo(
|
||||
((ushort) locale).ToString("x4", CultureInfo.InvariantCulture) + ((ushort) 1200).ToString("x4", CultureInfo.InvariantCulture));
|
||||
svi.Add(newStrings);
|
||||
this.dirty = true;
|
||||
|
||||
VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
|
||||
if (vvi == null)
|
||||
{
|
||||
vvi = new VersionInfo("VarFileInfo");
|
||||
vvi.Add(new VersionInfo("Translation"));
|
||||
this.rawVersionInfo.Add(vvi);
|
||||
}
|
||||
VersionInfo tVerInfo = vvi["Translation"];
|
||||
if (tVerInfo != null)
|
||||
{
|
||||
byte[] oldValue = tVerInfo.Data;
|
||||
if (oldValue == null) oldValue = new byte[0];
|
||||
tVerInfo.Data = new byte[oldValue.Length + 4];
|
||||
Array.Copy(oldValue, tVerInfo.Data, oldValue.Length);
|
||||
using (BinaryWriter bw = new BinaryWriter(new MemoryStream(tVerInfo.Data, oldValue.Length, 4, true)))
|
||||
{
|
||||
bw.Write((ushort) locale);
|
||||
bw.Write((ushort) 1200);
|
||||
}
|
||||
}
|
||||
|
||||
return new VersionStringTable(this, newStrings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a version string table for a locale.
|
||||
/// </summary>
|
||||
/// <param name="locale">Locale of the table</param>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public void Remove(int locale)
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi != null)
|
||||
{
|
||||
foreach (VersionInfo strings in svi)
|
||||
{
|
||||
int stringsLocale = UInt16.Parse(strings.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
if (stringsLocale == locale)
|
||||
{
|
||||
svi.Remove(strings);
|
||||
this.dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VersionInfo vvi = this.rawVersionInfo["VarFileInfo"];
|
||||
if (vvi != null)
|
||||
{
|
||||
VersionInfo tVerInfo = vvi["Translation"];
|
||||
if (tVerInfo != null)
|
||||
{
|
||||
byte[] newValue = new byte[tVerInfo.Data.Length];
|
||||
int j = 0;
|
||||
using (BinaryWriter bw = new BinaryWriter(new MemoryStream(newValue, 0, newValue.Length, true)))
|
||||
{
|
||||
using (BinaryReader br = new BinaryReader(new MemoryStream(tVerInfo.Data)))
|
||||
{
|
||||
for (int i = tVerInfo.Data.Length / 4; i > 0; i--)
|
||||
{
|
||||
ushort tLocale = br.ReadUInt16();
|
||||
ushort cp = br.ReadUInt16();
|
||||
if (tLocale != locale)
|
||||
{
|
||||
bw.Write((ushort) tLocale);
|
||||
bw.Write((ushort) cp);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tVerInfo.Data = new byte[j * 4];
|
||||
Array.Copy(newValue, tVerInfo.Data, tVerInfo.Data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a version string table exists for a given locale.
|
||||
/// </summary>
|
||||
/// <param name="locale">Locale to search for</param>
|
||||
/// <returns>True if a string table was found for the locale; false otherwise.</returns>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public bool Contains(int locale)
|
||||
{
|
||||
return this[locale] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number string tables in the version resource.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
return svi != null ? svi.Count : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all string tables from the version resource.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi != null)
|
||||
{
|
||||
svi.Clear();
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<VersionStringTable>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<VersionStringTable>.Add(VersionStringTable item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<VersionStringTable>.Remove(VersionStringTable item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<VersionStringTable>.Contains(VersionStringTable item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the version string tables to an array, starting at a particular array index.
|
||||
/// </summary>
|
||||
/// <param name="array">The one-dimensional Array that is the destination of the elements copied
|
||||
/// from the collection. The Array must have zero-based indexing.</param>
|
||||
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
|
||||
public void CopyTo(VersionStringTable[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) {
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi != null)
|
||||
{
|
||||
foreach (VersionInfo strings in svi)
|
||||
{
|
||||
array[arrayIndex++] = new VersionStringTable(this, strings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator that can iterate over the version string tables in the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
|
||||
public IEnumerator<VersionStringTable> GetEnumerator()
|
||||
{
|
||||
VersionInfo svi = this.rawVersionInfo["StringFileInfo"];
|
||||
if (svi != null)
|
||||
{
|
||||
foreach (VersionInfo strings in svi)
|
||||
{
|
||||
yield return new VersionStringTable(this, strings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator that can iterate over the version string tables in the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator that returns <see cref="VersionStringTable"/> objects.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="VersionStringTable.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.Resources
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a string table of a file version resource.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
|
||||
internal sealed class VersionStringTable : IDictionary<string, string>
|
||||
{
|
||||
private VersionResource parent;
|
||||
private VersionInfo rawStringVersionInfo;
|
||||
|
||||
internal VersionStringTable(VersionResource parent, VersionInfo rawStringVersionInfo)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.rawStringVersionInfo = rawStringVersionInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the locale (LCID) of the string table.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public int Locale
|
||||
{
|
||||
get
|
||||
{
|
||||
return UInt16.Parse(rawStringVersionInfo.Key.Substring(0, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
}
|
||||
set
|
||||
{
|
||||
rawStringVersionInfo.Key = ((ushort) value).ToString("x4", CultureInfo.InvariantCulture) + rawStringVersionInfo.Key.Substring(4, 4);
|
||||
this.parent.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a string value.
|
||||
/// </summary>
|
||||
/// <param name="key">Name of the string.</param>
|
||||
public string this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
VersionInfo verValue = this.rawStringVersionInfo[key];
|
||||
if (verValue == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2);
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
rawStringVersionInfo.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
VersionInfo verValue = rawStringVersionInfo[key];
|
||||
if (verValue == null)
|
||||
{
|
||||
verValue = new VersionInfo(key);
|
||||
verValue.IsString = true;
|
||||
rawStringVersionInfo.Add(verValue);
|
||||
}
|
||||
verValue.Data = new byte[Encoding.Unicode.GetByteCount(value) + 2];
|
||||
Encoding.Unicode.GetBytes(value, 0, value.Length, verValue.Data, 0);
|
||||
}
|
||||
this.parent.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.TryGetValue(string key, out string value)
|
||||
{
|
||||
value = this[key];
|
||||
return value != null;
|
||||
}
|
||||
|
||||
|
||||
void ICollection<KeyValuePair<string, string>>.Add(KeyValuePair<string, string> item)
|
||||
{
|
||||
this[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.Remove(KeyValuePair<string, string> item)
|
||||
{
|
||||
string value = this[item.Key];
|
||||
if (value == item.Value)
|
||||
{
|
||||
this[item.Key] = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, string>>.Contains(KeyValuePair<string, string> item)
|
||||
{
|
||||
string value = this[item.Key];
|
||||
if (value == item.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.ContainsKey(string key)
|
||||
{
|
||||
return this[key] != null;
|
||||
}
|
||||
|
||||
void IDictionary<string, string>.Add(string key, string value)
|
||||
{
|
||||
this[key] = value;
|
||||
}
|
||||
|
||||
bool IDictionary<string, string>.Remove(string key)
|
||||
{
|
||||
if (this[key] != null)
|
||||
{
|
||||
this[key] = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all strings from the string table.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
this.rawStringVersionInfo.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all the names of the strings in the table.
|
||||
/// </summary>
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> keys = new List<string>(this.rawStringVersionInfo.Count);
|
||||
foreach (VersionInfo verValue in this.rawStringVersionInfo)
|
||||
{
|
||||
keys.Add(verValue.Key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of all the values in the table.
|
||||
/// </summary>
|
||||
public ICollection<string> Values
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> values = new List<string>(this.rawStringVersionInfo.Count);
|
||||
foreach (VersionInfo verValue in this.rawStringVersionInfo)
|
||||
{
|
||||
values.Add(Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of strings in the table.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.rawStringVersionInfo.Count;
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, string>>.CopyTo(KeyValuePair<string, string>[] array, int index)
|
||||
{
|
||||
foreach (VersionInfo verValue in this.rawStringVersionInfo)
|
||||
{
|
||||
array[index++] = new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumeration over all strings in the table.
|
||||
/// </summary>
|
||||
/// <returns>Enumeration of string name and value pairs</returns>
|
||||
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
|
||||
{
|
||||
foreach (VersionInfo verValue in this.rawStringVersionInfo)
|
||||
{
|
||||
yield return new KeyValuePair<string, string>(verValue.Key, Encoding.Unicode.GetString(verValue.Data, 0, verValue.Data.Length - 2));
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="Attributes.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Apply to a subclass of QRecord to indicate the name of
|
||||
/// the table the record type is to be used with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this attribute is not used on a record type, the default
|
||||
/// table name will be derived from the record type name. (An
|
||||
/// optional underscore suffix is stripped.)
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
internal class DatabaseTableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new DatabaseTableAttribute for the specified table.
|
||||
/// </summary>
|
||||
/// <param name="table">name of the table associated with the record type</param>
|
||||
public DatabaseTableAttribute(string table)
|
||||
{
|
||||
this.Table = table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the table associated with the record type.
|
||||
/// </summary>
|
||||
public string Table { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply to a property on a subclass of QRecord to indicate
|
||||
/// the name of the column the property is to be associated with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this attribute is not used on a property, the default
|
||||
/// column name will be the same as the property name.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
|
||||
internal class DatabaseColumnAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new DatabaseColumnAttribute which maps a
|
||||
/// record property to a column.
|
||||
/// </summary>
|
||||
/// <param name="column">name of the column associated with the property</param>
|
||||
public DatabaseColumnAttribute(string column)
|
||||
{
|
||||
this.Column = column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the column associated with the record property.
|
||||
/// </summary>
|
||||
public string Column { get; set; }
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="Entities.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
// Silence warnings about style and doc-comments
|
||||
#if !CODE_ANALYSIS
|
||||
#pragma warning disable 1591
|
||||
#region Generated code
|
||||
|
||||
internal class Component_ : QRecord
|
||||
{
|
||||
public string Component { get { return this[0]; } set { this[0] = value; } }
|
||||
public string ComponentId { get { return this[1]; } set { this[1] = value; } }
|
||||
public string Directory_ { get { return this[2]; } set { this[2] = value; } }
|
||||
public string Condition { get { return this[4]; } set { this[4] = value; } }
|
||||
public string KeyPath { get { return this[5]; } set { this[5] = value; } }
|
||||
public ComponentAttributes Attributes
|
||||
{ get { return (ComponentAttributes) this.I(3); } set { this[3] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
internal class CreateFolder_ : QRecord
|
||||
{
|
||||
public string Directory_ { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Component_ { get { return this[1]; } set { this[1] = value; } }
|
||||
}
|
||||
|
||||
internal class CustomAction_ : QRecord
|
||||
{
|
||||
public string Action { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Source { get { return this[2]; } set { this[2] = value; } }
|
||||
public string Target { get { return this[3]; } set { this[3] = value; } }
|
||||
public CustomActionTypes Type
|
||||
{ get { return (CustomActionTypes) this.I(1); } set { this[1] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
internal class Directory_ : QRecord
|
||||
{
|
||||
public string Directory { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Directory_Parent { get { return this[1]; } set { this[1] = value; } }
|
||||
public string DefaultDir { get { return this[2]; } set { this[2] = value; } }
|
||||
}
|
||||
|
||||
internal class DuplicateFile_ : QRecord
|
||||
{
|
||||
public string FileKey { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Component_ { get { return this[1]; } set { this[1] = value; } }
|
||||
public string File_ { get { return this[2]; } set { this[2] = value; } }
|
||||
public string DestName { get { return this[4]; } set { this[4] = value; } }
|
||||
public string DestFolder { get { return this[5]; } set { this[5] = value; } }
|
||||
}
|
||||
|
||||
internal class Feature_ : QRecord
|
||||
{
|
||||
public string Feature { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Feature_Parent { get { return this[1]; } set { this[1] = value; } }
|
||||
public string Title { get { return this[2]; } set { this[2] = value; } }
|
||||
public string Description { get { return this[3]; } set { this[3] = value; } }
|
||||
public int? Display { get { return this.NI(4); } set { this[4] = value.ToString(); } }
|
||||
public int Level { get { return this.I(5); } set { this[5] = value.ToString(); } }
|
||||
public string Directory_ { get { return this[6]; } set { this[6] = value; } }
|
||||
public FeatureAttributes Attributes
|
||||
{ get { return (FeatureAttributes) this.I(7); } set { this[7] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
[DatabaseTable("FeatureComponents")]
|
||||
internal class FeatureComponent_ : QRecord
|
||||
{
|
||||
public string Feature_ { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Component_ { get { return this[1]; } set { this[1] = value; } }
|
||||
}
|
||||
|
||||
internal class File_ : QRecord
|
||||
{
|
||||
public string File { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Component_ { get { return this[1]; } set { this[1] = value; } }
|
||||
public string FileName { get { return this[2]; } set { this[2] = value; } }
|
||||
public int FileSize { get { return this.I(3); } set { this[3] = value.ToString(); } }
|
||||
public string Version { get { return this[4]; } set { this[4] = value; } }
|
||||
public string Language { get { return this[5]; } set { this[5] = value; } }
|
||||
public int Sequence { get { return this.I(7); } set { this[7] = value.ToString(); } }
|
||||
public FileAttributes Attributes
|
||||
{ get { return (FileAttributes) this.I(6); } set { this[6] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
[DatabaseTable("MsiFileHash")]
|
||||
internal class FileHash_ : QRecord
|
||||
{
|
||||
public string File_ { get { return this[0]; } set { this[0] = value; } }
|
||||
public int Options { get { return this.I(1); } set { this[1] = value.ToString(); } }
|
||||
public int HashPart1 { get { return this.I(2); } set { this[2] = value.ToString(); } }
|
||||
public int HashPart2 { get { return this.I(3); } set { this[3] = value.ToString(); } }
|
||||
public int HashPart3 { get { return this.I(4); } set { this[4] = value.ToString(); } }
|
||||
public int HashPart4 { get { return this.I(5); } set { this[5] = value.ToString(); } }
|
||||
}
|
||||
|
||||
[DatabaseTable("InstallExecuteSequence")]
|
||||
internal class InstallSequence_ : QRecord
|
||||
{
|
||||
public string Action { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Condition { get { return this[1]; } set { this[1] = value; } }
|
||||
public int Sequence { get { return this.I(2); } set { this[2] = value.ToString(); } }
|
||||
}
|
||||
|
||||
internal class LaunchCondition_ : QRecord
|
||||
{
|
||||
public string Condition { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Description { get { return this[1]; } set { this[1] = value; } }
|
||||
}
|
||||
|
||||
internal class Media_ : QRecord
|
||||
{
|
||||
public int DiskId { get { return this.I(0); } set { this[0] = value.ToString(); } }
|
||||
public int LastSequence { get { return this.I(1); } set { this[1] = value.ToString(); } }
|
||||
public string DiskPrompt { get { return this[2]; } set { this[2] = value; } }
|
||||
public string Cabinet { get { return this[3]; } set { this[3] = value; } }
|
||||
public string VolumeLabel { get { return this[4]; } set { this[4] = value; } }
|
||||
public string Source { get { return this[5]; } set { this[5] = value; } }
|
||||
}
|
||||
|
||||
internal class Property_ : QRecord
|
||||
{
|
||||
public string Property { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Value { get { return this[1]; } set { this[1] = value; } }
|
||||
}
|
||||
|
||||
internal class Registry_ : QRecord
|
||||
{
|
||||
public string Registry { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Key { get { return this[2]; } set { this[2] = value; } }
|
||||
public string Name { get { return this[3]; } set { this[3] = value; } }
|
||||
public string Value { get { return this[4]; } set { this[4] = value; } }
|
||||
public string Component_ { get { return this[5]; } set { this[5] = value; } }
|
||||
public RegistryRoot Root
|
||||
{ get { return (RegistryRoot) this.I(1); } set { this[0] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
internal class RemoveFile_ : QRecord
|
||||
{
|
||||
public string FileKey { get { return this[0]; } set { this[0] = value; } }
|
||||
public string Component_ { get { return this[2]; } set { this[2] = value; } }
|
||||
public string FileName { get { return this[3]; } set { this[3] = value; } }
|
||||
public string DirProperty { get { return this[4]; } set { this[4] = value; } }
|
||||
public RemoveFileModes InstallMode
|
||||
{ get { return (RemoveFileModes) this.I(5); } set { this[5] = ((int) value).ToString(); } }
|
||||
}
|
||||
|
||||
#endregion // Generated code
|
||||
#pragma warning restore 1591
|
||||
#endif // !CODE_ANALYSIS
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="QDatabase.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Allows any Database instance to be converted into a queryable database.
|
||||
/// </summary>
|
||||
internal static class Queryable
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts any Database instance into a queryable database.
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <returns>Queryable database instance that operates on the same
|
||||
/// MSI handle.</returns>
|
||||
/// <remarks>
|
||||
/// This extension method is meant for convenient on-the-fly conversion.
|
||||
/// If the existing database instance already happens to be a QDatabase,
|
||||
/// then it is returned unchanged. Otherwise since the new database
|
||||
/// carries the same MSI handle, only one of the instances needs to be
|
||||
/// closed, not both.
|
||||
/// </remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public static QDatabase AsQueryable(this Database db)
|
||||
{
|
||||
QDatabase qdb = db as QDatabase;
|
||||
if (qdb == null && db != null)
|
||||
{
|
||||
qdb = new QDatabase(db.Handle, true, db.FilePath, db.OpenMode);
|
||||
}
|
||||
return qdb;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queryable MSI database - extends the base Database class with
|
||||
/// LINQ query functionality along with predefined entity types
|
||||
/// for common tables.
|
||||
/// </summary>
|
||||
internal class QDatabase : Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Opens an existing database in read-only mode.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the database file.</param>
|
||||
/// <exception cref="InstallerException">the database could not be created/opened</exception>
|
||||
/// <remarks>
|
||||
/// Because this constructor initiates database access, it cannot be used with a
|
||||
/// running installation.
|
||||
/// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
|
||||
/// The finalizer will close the handle if it is still open, however due to the nondeterministic
|
||||
/// nature of finalization it is best that the handle be closed manually as soon as it is no
|
||||
/// longer needed, as leaving lots of unused handles open can degrade performance.</para>
|
||||
/// </remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public QDatabase(string filePath)
|
||||
: base(filePath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing database with another database as output.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the database to be read.</param>
|
||||
/// <param name="outputPath">Open mode for the database</param>
|
||||
/// <returns>Database object representing the created or opened database</returns>
|
||||
/// <exception cref="InstallerException">the database could not be created/opened</exception>
|
||||
/// <remarks>
|
||||
/// When a database is opened as the output of another database, the summary information stream
|
||||
/// of the output database is actually a read-only mirror of the original database and thus cannot
|
||||
/// be changed. Additionally, it is not persisted with the database. To create or modify the
|
||||
/// summary information for the output database it must be closed and re-opened.
|
||||
/// <para>The returned Database object should be <see cref="InstallerHandle.Close"/>d after use.
|
||||
/// The finalizer will close the handle if it is still open, however due to the nondeterministic
|
||||
/// nature of finalization it is best that the handle be closed manually as soon as it is no
|
||||
/// longer needed, as leaving lots of unused handles open can degrade performance.</para>
|
||||
/// </remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public QDatabase(string filePath, string outputPath)
|
||||
: base(filePath, outputPath)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing database or creates a new one.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to the database file. If an empty string
|
||||
/// is supplied, a temporary database is created that is not persisted.</param>
|
||||
/// <param name="mode">Open mode for the database</param>
|
||||
/// <exception cref="InstallerException">the database could not be created/opened</exception>
|
||||
/// <remarks>
|
||||
/// To make and save changes to a database first open the database in transaction,
|
||||
/// create or, or direct mode. After making the changes, always call the Commit method
|
||||
/// before closing the database handle. The Commit method flushes all buffers.
|
||||
/// <para>Always call the Commit method on a database that has been opened in direct
|
||||
/// mode before closing the database. Failure to do this may corrupt the database.</para>
|
||||
/// <para>Because this constructor initiates database access, it cannot be used with a
|
||||
/// running installation.</para>
|
||||
/// <para>The Database object should be <see cref="InstallerHandle.Close"/>d after use.
|
||||
/// The finalizer will close the handle if it is still open, however due to the nondeterministic
|
||||
/// nature of finalization it is best that the handle be closed manually as soon as it is no
|
||||
/// longer needed, as leaving lots of unused handles open can degrade performance.</para>
|
||||
/// </remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public QDatabase(string filePath, DatabaseOpenMode mode)
|
||||
: base(filePath, mode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new database from an MSI handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">Native MSI database handle.</param>
|
||||
/// <param name="ownsHandle">True if the handle should be closed
|
||||
/// when the database object is disposed</param>
|
||||
/// <param name="filePath">Path of the database file, if known</param>
|
||||
/// <param name="openMode">Mode the handle was originally opened in</param>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
protected internal QDatabase(
|
||||
IntPtr handle, bool ownsHandle, string filePath, DatabaseOpenMode openMode)
|
||||
: base(handle, ownsHandle, filePath, openMode)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a log where all MSI SQL queries are written.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The log can be useful for debugging, or simply to watch the LINQ magic in action.
|
||||
/// </remarks>
|
||||
public TextWriter Log { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a queryable table from the datbaase.
|
||||
/// </summary>
|
||||
/// <param name="table">name of the table</param>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public QTable<QRecord> this[string table]
|
||||
{
|
||||
get
|
||||
{
|
||||
return new QTable<QRecord>(this, table);
|
||||
}
|
||||
}
|
||||
|
||||
#if !CODE_ANALYSIS
|
||||
#region Queryable tables
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Component_> Components
|
||||
{ get { return new QTable<Component_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<CreateFolder_> CreateFolders
|
||||
{ get { return new QTable<CreateFolder_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<CustomAction_> CustomActions
|
||||
{ get { return new QTable<CustomAction_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Directory_> Directories
|
||||
{ get { return new QTable<Directory_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<DuplicateFile_> DuplicateFiles
|
||||
{ get { return new QTable<DuplicateFile_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Feature_> Features
|
||||
{ get { return new QTable<Feature_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<FeatureComponent_> FeatureComponents
|
||||
{ get { return new QTable<FeatureComponent_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<File_> Files
|
||||
{ get { return new QTable<File_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<FileHash_> FileHashes
|
||||
{ get { return new QTable<FileHash_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<InstallSequence_> InstallExecuteSequences
|
||||
{ get { return new QTable<InstallSequence_>(this, "InstallExecuteSequence"); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<InstallSequence_> InstallUISequences
|
||||
{ get { return new QTable<InstallSequence_>(this, "InstallUISequence"); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<LaunchCondition_> LaunchConditions
|
||||
{ get { return new QTable<LaunchCondition_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Media_> Medias
|
||||
{ get { return new QTable<Media_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Property_> Properties
|
||||
{ get { return new QTable<Property_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<Registry_> Registries
|
||||
{ get { return new QTable<Registry_>(this); } }
|
||||
|
||||
/// <summary>Queryable standard table with predefined specialized record type.</summary>
|
||||
public QTable<RemoveFile_> RemoveFiles
|
||||
{ get { return new QTable<RemoveFile_>(this); } }
|
||||
|
||||
#endregion // Queryable tables
|
||||
#endif // !CODE_ANALYSIS
|
||||
}
|
||||
}
|
||||
+508
@@ -0,0 +1,508 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="QRecord.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Generic record entity for queryable databases,
|
||||
/// and base for strongly-typed entity subclasses.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Several predefined specialized subclasses are provided for common
|
||||
/// standard tables. Subclasses for additional standard tables
|
||||
/// or custom tables are not necessary, but they are easy to create
|
||||
/// and make the coding experience much nicer.
|
||||
/// <para>When creating subclasses, the following attributes may be
|
||||
/// useful: <see cref="DatabaseTableAttribute"/>,
|
||||
/// <see cref="DatabaseColumnAttribute"/></para>
|
||||
/// </remarks>
|
||||
internal class QRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not call. Use QTable.NewRecord() instead.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Subclasses must also provide a public parameterless constructor.
|
||||
/// <para>QRecord constructors are only public due to implementation
|
||||
/// reasons (to satisfy the new() constraint on the QTable generic
|
||||
/// class). They are not intended to be called by user code other than
|
||||
/// a subclass constructor. If the constructor is invoked directly,
|
||||
/// the record instance will not be properly initialized (associated
|
||||
/// with a database table) and calls to methods on the instance
|
||||
/// will throw a NullReferenceException.</para>
|
||||
/// </remarks>
|
||||
/// <seealso cref="QTable<TRecord>.NewRecord()"/>
|
||||
public QRecord()
|
||||
{
|
||||
}
|
||||
|
||||
internal QDatabase Database { get; set; }
|
||||
|
||||
internal TableInfo TableInfo { get; set; }
|
||||
|
||||
internal IList<string> Values { get; set; }
|
||||
|
||||
internal bool Exists { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of fields in the record.
|
||||
/// </summary>
|
||||
public int FieldCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Values.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a record field.
|
||||
/// </summary>
|
||||
/// <param name="field">column name of the field</param>
|
||||
/// <remarks>
|
||||
/// Setting a field value will automatically update the database.
|
||||
/// </remarks>
|
||||
public string this[string field]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (field == null)
|
||||
{
|
||||
throw new ArgumentNullException("field");
|
||||
}
|
||||
|
||||
int index = this.TableInfo.Columns.IndexOf(field);
|
||||
if (index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("field");
|
||||
}
|
||||
|
||||
return this[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (field == null)
|
||||
{
|
||||
throw new ArgumentNullException("field");
|
||||
}
|
||||
|
||||
this.Update(new string[] { field }, new string[] { value });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a record field.
|
||||
/// </summary>
|
||||
/// <param name="index">zero-based column index of the field</param>
|
||||
/// <remarks>
|
||||
/// Setting a field value will automatically update the database.
|
||||
/// </remarks>
|
||||
public string this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= this.FieldCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
}
|
||||
|
||||
return this.Values[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= this.FieldCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("index");
|
||||
}
|
||||
|
||||
this.Update(new int[] { index }, new string[] { value });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by subclasses to get a field as an integer.
|
||||
/// </summary>
|
||||
/// <param name="index">zero-based column index of the field</param>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "I")]
|
||||
protected int I(int index)
|
||||
{
|
||||
string value = this[index];
|
||||
return value.Length > 0 ?
|
||||
Int32.Parse(value, CultureInfo.InvariantCulture) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by subclasses to get a field as a nullable integer.
|
||||
/// </summary>
|
||||
/// <param name="index">zero-based column index of the field</param>
|
||||
protected int? NI(int index)
|
||||
{
|
||||
string value = this[index];
|
||||
return value.Length > 0 ?
|
||||
new int?(Int32.Parse(value, CultureInfo.InvariantCulture)) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dumps all record fields to a string.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(this.GetType().Name);
|
||||
buf.Append(" {");
|
||||
for (int i = 0; i < this.FieldCount; i++)
|
||||
{
|
||||
buf.AppendFormat("{0} {1} = {2}",
|
||||
(i > 0 ? "," : String.Empty),
|
||||
this.TableInfo.Columns[i].Name,
|
||||
this[i]);
|
||||
}
|
||||
buf.Append(" }");
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update multiple fields in the record (and the database).
|
||||
/// </summary>
|
||||
/// <param name="fields">column names of fields to update</param>
|
||||
/// <param name="values">new values for each field being updated</param>
|
||||
public void Update(IList<string> fields, IList<string> values)
|
||||
{
|
||||
if (fields == null)
|
||||
{
|
||||
throw new ArgumentNullException("fields");
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException("values");
|
||||
}
|
||||
|
||||
if (fields.Count == 0 || values.Count == 0 ||
|
||||
fields.Count > this.FieldCount ||
|
||||
values.Count != fields.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("fields");
|
||||
}
|
||||
|
||||
int[] indexes = new int[fields.Count];
|
||||
for (int i = 0; i < indexes.Length; i++)
|
||||
{
|
||||
if (fields[i] == null)
|
||||
{
|
||||
throw new ArgumentNullException("fields[" + i + "]");
|
||||
}
|
||||
|
||||
indexes[i] = this.TableInfo.Columns.IndexOf(fields[i]);
|
||||
|
||||
if (indexes[i] < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("fields[" + i + "]");
|
||||
}
|
||||
}
|
||||
|
||||
this.Update(indexes, values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update multiple fields in the record (and the database).
|
||||
/// </summary>
|
||||
/// <param name="indexes">column indexes of fields to update</param>
|
||||
/// <param name="values">new values for each field being updated</param>
|
||||
/// <remarks>
|
||||
/// The record (primary keys) must already exist in the table.
|
||||
/// <para>Updating primary key fields is not yet implemented; use Delete()
|
||||
/// and Insert() instead.</para>
|
||||
/// </remarks>
|
||||
public void Update(IList<int> indexes, IList<string> values)
|
||||
{
|
||||
if (indexes == null)
|
||||
{
|
||||
throw new ArgumentNullException("indexes");
|
||||
}
|
||||
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException("values");
|
||||
}
|
||||
|
||||
if (indexes.Count == 0 || values.Count == 0 ||
|
||||
indexes.Count > this.FieldCount ||
|
||||
values.Count != indexes.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("indexes");
|
||||
}
|
||||
|
||||
bool primaryKeyChanged = false;
|
||||
for (int i = 0; i < indexes.Count; i++)
|
||||
{
|
||||
int index = indexes[i];
|
||||
if (index < 0 || index >= this.FieldCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("index[" + i + "]");
|
||||
}
|
||||
|
||||
ColumnInfo col = this.TableInfo.Columns[index];
|
||||
if (this.TableInfo.PrimaryKeys.Contains(col.Name))
|
||||
{
|
||||
if (values[i] == null)
|
||||
{
|
||||
throw new ArgumentNullException("values[" + i + "]");
|
||||
}
|
||||
|
||||
primaryKeyChanged = true;
|
||||
}
|
||||
else if (values[i] == null)
|
||||
{
|
||||
if (col.IsRequired)
|
||||
{
|
||||
throw new ArgumentNullException("values[" + i + "]");
|
||||
}
|
||||
}
|
||||
|
||||
this.Values[index] = values[i];
|
||||
}
|
||||
|
||||
if (this.Exists)
|
||||
{
|
||||
if (!primaryKeyChanged)
|
||||
{
|
||||
int updateRecSize = indexes.Count + this.TableInfo.PrimaryKeys.Count;
|
||||
using (Record updateRec = this.Database.CreateRecord(updateRecSize))
|
||||
{
|
||||
StringBuilder s = new StringBuilder("UPDATE `");
|
||||
s.Append(this.TableInfo.Name);
|
||||
s.Append("` SET");
|
||||
|
||||
for (int i = 0; i < indexes.Count; i++)
|
||||
{
|
||||
ColumnInfo col = this.TableInfo.Columns[indexes[i]];
|
||||
if (col.Type == typeof(Stream))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Cannot update stream columns via QRecord.");
|
||||
}
|
||||
|
||||
int index = indexes[i];
|
||||
s.AppendFormat("{0} `{1}` = ?",
|
||||
(i > 0 ? "," : String.Empty),
|
||||
col.Name);
|
||||
|
||||
if (values[i] != null)
|
||||
{
|
||||
updateRec[i + 1] = values[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
|
||||
{
|
||||
string key = this.TableInfo.PrimaryKeys[i];
|
||||
s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
|
||||
int index = this.TableInfo.Columns.IndexOf(key);
|
||||
updateRec[indexes.Count + i + 1] = this.Values[index];
|
||||
|
||||
}
|
||||
|
||||
string updateSql = s.ToString();
|
||||
TextWriter log = this.Database.Log;
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine();
|
||||
log.WriteLine(updateSql);
|
||||
for (int field = 1; field <= updateRecSize; field++)
|
||||
{
|
||||
log.WriteLine(" ? = " + updateRec.GetString(field));
|
||||
}
|
||||
}
|
||||
|
||||
this.Database.Execute(updateSql, updateRec);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(
|
||||
"Update() cannot handle changed primary keys yet.");
|
||||
// TODO:
|
||||
// query using old values
|
||||
// update values
|
||||
// View.Replace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the record in the database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The record (primary keys) may not already exist in the table.
|
||||
/// <para>Use <see cref="QTable<TRecord>.NewRecord()"/> to get a new
|
||||
/// record. Prmary keys and all required fields
|
||||
/// must be filled in before insertion.</para>
|
||||
/// </remarks>
|
||||
public void Insert()
|
||||
{
|
||||
this.Insert(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the record into the table.
|
||||
/// </summary>
|
||||
/// <param name="temporary">true if the record is temporarily
|
||||
/// inserted, to be visible only as long as the database is open</param>
|
||||
/// <remarks>
|
||||
/// The record (primary keys) may not already exist in the table.
|
||||
/// <para>Use <see cref="QTable<TRecord>.NewRecord()"/> to get a new
|
||||
/// record. Prmary keys and all required fields
|
||||
/// must be filled in before insertion.</para>
|
||||
/// </remarks>
|
||||
public void Insert(bool temporary)
|
||||
{
|
||||
using (Record updateRec = this.Database.CreateRecord(this.FieldCount))
|
||||
{
|
||||
string insertSql = this.TableInfo.SqlInsertString;
|
||||
if (temporary)
|
||||
{
|
||||
insertSql += " TEMPORARY";
|
||||
}
|
||||
|
||||
TextWriter log = this.Database.Log;
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine();
|
||||
log.WriteLine(insertSql);
|
||||
}
|
||||
|
||||
for (int index = 0; index < this.FieldCount; index++)
|
||||
{
|
||||
ColumnInfo col = this.TableInfo.Columns[index];
|
||||
if (col.Type == typeof(Stream))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Cannot insert stream columns via QRecord.");
|
||||
}
|
||||
|
||||
if (this.Values[index] != null)
|
||||
{
|
||||
updateRec[index + 1] = this.Values[index];
|
||||
}
|
||||
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine(" ? = " + this.Values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
this.Database.Execute(insertSql, updateRec);
|
||||
this.Exists = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the record from the table if it exists.
|
||||
/// </summary>
|
||||
public void Delete()
|
||||
{
|
||||
using (Record keyRec = this.Database.CreateRecord(this.TableInfo.PrimaryKeys.Count))
|
||||
{
|
||||
StringBuilder s = new StringBuilder("DELETE FROM `");
|
||||
s.Append(this.TableInfo.Name);
|
||||
s.Append("`");
|
||||
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
|
||||
{
|
||||
string key = this.TableInfo.PrimaryKeys[i];
|
||||
s.AppendFormat(" {0} `{1}` = ?", (i == 0 ? "WHERE" : "AND"), key);
|
||||
int index = this.TableInfo.Columns.IndexOf(key);
|
||||
keyRec[i + 1] = this.Values[index];
|
||||
}
|
||||
|
||||
string deleteSql = s.ToString();
|
||||
|
||||
TextWriter log = this.Database.Log;
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine();
|
||||
log.WriteLine(deleteSql);
|
||||
|
||||
for (int i = 0; i < this.TableInfo.PrimaryKeys.Count; i++)
|
||||
{
|
||||
log.WriteLine(" ? = " + keyRec.GetString(i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
this.Database.Execute(deleteSql, keyRec);
|
||||
this.Exists = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public void Assign()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public bool Merge()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public ICollection<ValidationErrorInfo> Validate()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
|
||||
public ICollection<ValidationErrorInfo> ValidateNew()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public ICollection<ValidationErrorInfo> ValidateFields()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not yet implemented.
|
||||
/// </summary>
|
||||
public ICollection<ValidationErrorInfo> ValidateDelete()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="QTable.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents one table in a LINQ-queryable Database.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRecord">type that represents one record in the table</typeparam>
|
||||
/// <remarks>
|
||||
/// This class is the primary gateway to all LINQ to MSI query functionality.
|
||||
/// <para>The TRecord generic parameter may be the general <see cref="QRecord" />
|
||||
/// class, or a specialized subclass of QRecord.</para>
|
||||
/// </remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
|
||||
internal sealed class QTable<TRecord> : IOrderedQueryable<TRecord>, IQueryProvider
|
||||
where TRecord : QRecord, new()
|
||||
{
|
||||
private QDatabase db;
|
||||
private TableInfo tableInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Infers the name of the table this instance will be
|
||||
/// associated with.
|
||||
/// </summary>
|
||||
/// <returns>table name</returns>
|
||||
/// <remarks>
|
||||
/// The table name is retrieved from a DatabaseTableAttribute
|
||||
/// on the record type if it exists; otherwise the name is
|
||||
/// derived from the name of the record type itself.
|
||||
/// (An optional underscore suffix on the record type name is dropped.)
|
||||
/// </remarks>
|
||||
private static string InferTableName()
|
||||
{
|
||||
foreach (DatabaseTableAttribute attr in typeof(TRecord).GetCustomAttributes(
|
||||
typeof(DatabaseTableAttribute), false))
|
||||
{
|
||||
string tableName = attr.Table;
|
||||
if (!string.IsNullOrWhiteSpace(tableName))
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
}
|
||||
|
||||
string recordTypeName = typeof(TRecord).Name;
|
||||
if (recordTypeName[recordTypeName.Length - 1] == '_')
|
||||
{
|
||||
return recordTypeName.Substring(0, recordTypeName.Length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return recordTypeName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new QTable, inferring the table name
|
||||
/// from the name of the record type parameter.
|
||||
/// </summary>
|
||||
/// <param name="db">database that contains the table</param>
|
||||
public QTable(QDatabase db)
|
||||
: this(db, InferTableName())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new QTable with an explicit table name.
|
||||
/// </summary>
|
||||
/// <param name="db">database that contains the table</param>
|
||||
/// <param name="table">name of the table</param>
|
||||
public QTable(QDatabase db, string table)
|
||||
{
|
||||
if (db == null)
|
||||
{
|
||||
throw new ArgumentNullException("db");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(table))
|
||||
{
|
||||
throw new ArgumentNullException("table");
|
||||
}
|
||||
|
||||
this.db = db;
|
||||
this.tableInfo = db.Tables[table];
|
||||
if (this.tableInfo == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Table does not exist in database: " + table);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets schema information about the table.
|
||||
/// </summary>
|
||||
public TableInfo TableInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.tableInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database this table is associated with.
|
||||
/// </summary>
|
||||
public QDatabase Database
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.db;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates over all records in the table.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<TRecord> GetEnumerator()
|
||||
{
|
||||
string query = this.tableInfo.SqlSelectString;
|
||||
|
||||
TextWriter log = this.db.Log;
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine();
|
||||
log.WriteLine(query);
|
||||
}
|
||||
|
||||
using (View view = db.OpenView(query))
|
||||
{
|
||||
view.Execute();
|
||||
|
||||
ColumnCollection columns = this.tableInfo.Columns;
|
||||
int columnCount = columns.Count;
|
||||
bool[] isBinary = new bool[columnCount];
|
||||
|
||||
for (int i = 0; i < isBinary.Length; i++)
|
||||
{
|
||||
isBinary[i] = columns[i].Type == typeof(System.IO.Stream);
|
||||
}
|
||||
|
||||
foreach (Record rec in view) using (rec)
|
||||
{
|
||||
string[] values = new string[columnCount];
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = isBinary[i] ? "[Binary Data]" : rec.GetString(i + 1);
|
||||
}
|
||||
|
||||
TRecord trec = new TRecord();
|
||||
trec.Database = this.Database;
|
||||
trec.TableInfo = this.TableInfo;
|
||||
trec.Values = values;
|
||||
trec.Exists = true;
|
||||
yield return trec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<TRecord>) this).GetEnumerator();
|
||||
}
|
||||
|
||||
IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
{
|
||||
throw new ArgumentNullException("expression");
|
||||
}
|
||||
|
||||
Query<TElement> q = new Query<TElement>(this.Database, expression);
|
||||
|
||||
MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
|
||||
string methodName = methodCallExpression.Method.Name;
|
||||
if (methodName == "Where")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildQuery(this.TableInfo, argumentExpression);
|
||||
}
|
||||
else if (methodName == "OrderBy")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildSequence(this.TableInfo, argumentExpression);
|
||||
}
|
||||
else if (methodName == "Select")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildNullQuery(this.TableInfo, typeof(TRecord), argumentExpression);
|
||||
q.BuildProjection(null, argumentExpression);
|
||||
}
|
||||
else if (methodName == "Join")
|
||||
{
|
||||
ConstantExpression constantExpression = (ConstantExpression)
|
||||
methodCallExpression.Arguments[1];
|
||||
IQueryable inner = (IQueryable) constantExpression.Value;
|
||||
q.PerformJoin(
|
||||
this.TableInfo,
|
||||
typeof(TRecord),
|
||||
inner,
|
||||
GetJoinLambda(methodCallExpression.Arguments[2]),
|
||||
GetJoinLambda(methodCallExpression.Arguments[3]),
|
||||
GetJoinLambda(methodCallExpression.Arguments[4]));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Query operation not supported: " + methodName);
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
private static LambdaExpression GetJoinLambda(Expression expresion)
|
||||
{
|
||||
UnaryExpression unaryExpression = (UnaryExpression) expresion;
|
||||
return (LambdaExpression) unaryExpression.Operand;
|
||||
}
|
||||
|
||||
IQueryable IQueryProvider.CreateQuery(Expression expression)
|
||||
{
|
||||
return ((IQueryProvider) this).CreateQuery<TRecord>(expression);
|
||||
}
|
||||
|
||||
TResult IQueryProvider.Execute<TResult>(Expression expression)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Direct method calls not supported -- use AsEnumerable() instead.");
|
||||
}
|
||||
|
||||
object IQueryProvider.Execute(Expression expression)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Direct method calls not supported -- use AsEnumerable() instead.");
|
||||
}
|
||||
|
||||
IQueryProvider IQueryable.Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
Type IQueryable.ElementType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(TRecord);
|
||||
}
|
||||
}
|
||||
|
||||
Expression IQueryable.Expression
|
||||
{
|
||||
get
|
||||
{
|
||||
return Expression.Constant(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new record that can be inserted into this table.
|
||||
/// </summary>
|
||||
/// <returns>a record with all fields initialized to null</returns>
|
||||
/// <remarks>
|
||||
/// Primary keys and required fields must be filled in with
|
||||
/// non-null values before the record can be inserted.
|
||||
/// <para>The record is tied to this table in this database;
|
||||
/// it cannot be inserted into another table or database.</para>
|
||||
/// </remarks>
|
||||
public TRecord NewRecord()
|
||||
{
|
||||
TRecord rec = new TRecord();
|
||||
rec.Database = this.Database;
|
||||
rec.TableInfo = this.TableInfo;
|
||||
IList<string> values = new List<string>(this.TableInfo.Columns.Count);
|
||||
for (int i = 0; i < this.TableInfo.Columns.Count; i++)
|
||||
{
|
||||
values.Add(null);
|
||||
}
|
||||
rec.Values = values;
|
||||
return rec;
|
||||
}
|
||||
}
|
||||
}
|
||||
+999
@@ -0,0 +1,999 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="Query.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Linq
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the LINQ to MSI query functionality.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">the result type of the current query --
|
||||
/// either some kind of QRecord, or some projection of record data</typeparam>
|
||||
internal sealed class Query<T> : IOrderedQueryable<T>, IQueryProvider
|
||||
{
|
||||
private QDatabase db;
|
||||
private Expression queryableExpression;
|
||||
private List<TableInfo> tables;
|
||||
private List<Type> recordTypes;
|
||||
private List<string> selectors;
|
||||
private string where;
|
||||
private List<object> whereParameters;
|
||||
private List<TableColumn> orderbyColumns;
|
||||
private List<TableColumn> selectColumns;
|
||||
private List<TableColumn> joinColumns;
|
||||
private List<Delegate> projectionDelegates;
|
||||
|
||||
internal Query(QDatabase db, Expression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
{
|
||||
throw new ArgumentNullException("expression");
|
||||
}
|
||||
|
||||
this.db = db;
|
||||
this.queryableExpression = expression;
|
||||
this.tables = new List<TableInfo>();
|
||||
this.recordTypes = new List<Type>();
|
||||
this.selectors = new List<string>();
|
||||
this.whereParameters = new List<object>();
|
||||
this.orderbyColumns = new List<TableColumn>();
|
||||
this.selectColumns = new List<TableColumn>();
|
||||
this.joinColumns = new List<TableColumn>();
|
||||
this.projectionDelegates = new List<Delegate>();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
if (this.selectColumns.Count == 0)
|
||||
{
|
||||
AddAllColumns(this.tables[0], this.selectColumns);
|
||||
}
|
||||
|
||||
string query = this.CompileQuery();
|
||||
return this.InvokeQuery(query);
|
||||
}
|
||||
|
||||
private string CompileQuery()
|
||||
{
|
||||
bool explicitTables = this.tables.Count > 1;
|
||||
|
||||
StringBuilder queryBuilder = new StringBuilder("SELECT");
|
||||
|
||||
for (int i = 0; i < this.selectColumns.Count; i++)
|
||||
{
|
||||
queryBuilder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
(explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
|
||||
(i > 0 ? "," : String.Empty),
|
||||
this.selectColumns[i].Table.Name,
|
||||
this.selectColumns[i].Column.Name);
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
queryBuilder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} `{1}`",
|
||||
(i == 0 ? " FROM" : ","),
|
||||
this.tables[i].Name);
|
||||
}
|
||||
|
||||
bool startedWhere = false;
|
||||
for (int i = 0; i < this.joinColumns.Count - 1; i += 2)
|
||||
{
|
||||
queryBuilder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} `{1}`.`{2}` = `{3}`.`{4}` ",
|
||||
(i == 0 ? " WHERE" : "AND"),
|
||||
this.joinColumns[i].Table,
|
||||
this.joinColumns[i].Column,
|
||||
this.joinColumns[i + 1].Table,
|
||||
this.joinColumns[i + 1].Column);
|
||||
startedWhere = true;
|
||||
}
|
||||
|
||||
if (this.where != null)
|
||||
{
|
||||
queryBuilder.Append(startedWhere ? "AND " : " WHERE");
|
||||
queryBuilder.Append(this.where);
|
||||
}
|
||||
|
||||
for (int i = 0; i < this.orderbyColumns.Count; i++)
|
||||
{
|
||||
VerifyOrderByColumn(this.orderbyColumns[i]);
|
||||
|
||||
queryBuilder.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
(explicitTables ? "{0} `{1}`.`{2}`" : "{0} `{2}`"),
|
||||
(i == 0 ? " ORDER BY" : ","),
|
||||
this.orderbyColumns[i].Table.Name,
|
||||
this.orderbyColumns[i].Column.Name);
|
||||
}
|
||||
|
||||
return queryBuilder.ToString();
|
||||
}
|
||||
|
||||
private static void VerifyOrderByColumn(TableColumn tableColumn)
|
||||
{
|
||||
if (tableColumn.Column.Type != typeof(int) &&
|
||||
tableColumn.Column.Type != typeof(short))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Cannot orderby column: " + tableColumn.Column.Name +
|
||||
"; orderby is only supported on integer fields");
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator<T> InvokeQuery(string query)
|
||||
{
|
||||
TextWriter log = this.db.Log;
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine();
|
||||
log.WriteLine(query);
|
||||
}
|
||||
|
||||
using (View queryView = this.db.OpenView(query))
|
||||
{
|
||||
if (this.whereParameters != null && this.whereParameters.Count > 0)
|
||||
{
|
||||
using (Record paramsRec = this.db.CreateRecord(this.whereParameters.Count))
|
||||
{
|
||||
for (int i = 0; i < this.whereParameters.Count; i++)
|
||||
{
|
||||
paramsRec[i + 1] = this.whereParameters[i];
|
||||
|
||||
if (log != null)
|
||||
{
|
||||
log.WriteLine(" ? = " + this.whereParameters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
queryView.Execute(paramsRec);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queryView.Execute();
|
||||
}
|
||||
|
||||
foreach (Record resultRec in queryView) using (resultRec)
|
||||
{
|
||||
yield return this.GetResult(resultRec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private T GetResult(Record resultRec)
|
||||
{
|
||||
object[] results = new object[this.tables.Count];
|
||||
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
string[] values = new string[this.tables[i].Columns.Count];
|
||||
for (int j = 0; j < this.selectColumns.Count; j++)
|
||||
{
|
||||
TableColumn col = this.selectColumns[j];
|
||||
if (col.Table.Name == this.tables[i].Name)
|
||||
{
|
||||
int index = this.tables[i].Columns.IndexOf(
|
||||
col.Column.Name);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (col.Column.Type == typeof(Stream))
|
||||
{
|
||||
values[index] = "[Binary Data]";
|
||||
}
|
||||
else
|
||||
{
|
||||
values[index] = resultRec.GetString(j + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRecord result = (QRecord) this.recordTypes[i]
|
||||
.GetConstructor(Type.EmptyTypes).Invoke(null);
|
||||
result.Database = this.db;
|
||||
result.TableInfo = this.tables[i];
|
||||
result.Values = values;
|
||||
result.Exists = true;
|
||||
results[i] = result;
|
||||
}
|
||||
|
||||
if (this.projectionDelegates.Count > 0)
|
||||
{
|
||||
object resultsProjection = results[0];
|
||||
for (int i = 1; i <= results.Length; i++)
|
||||
{
|
||||
if (i < results.Length)
|
||||
{
|
||||
resultsProjection = this.projectionDelegates[i - 1]
|
||||
.DynamicInvoke(new object[] { resultsProjection, results[i] });
|
||||
}
|
||||
else
|
||||
{
|
||||
resultsProjection = this.projectionDelegates[i - 1]
|
||||
.DynamicInvoke(resultsProjection);
|
||||
}
|
||||
}
|
||||
|
||||
return (T) resultsProjection;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (T) (object) results[0];
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>) this).GetEnumerator();
|
||||
}
|
||||
|
||||
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
{
|
||||
throw new ArgumentNullException("expression");
|
||||
}
|
||||
|
||||
Query<TElement> q = new Query<TElement>(this.db, expression);
|
||||
q.tables.AddRange(this.tables);
|
||||
q.recordTypes.AddRange(this.recordTypes);
|
||||
q.selectors.AddRange(this.selectors);
|
||||
q.where = this.where;
|
||||
q.whereParameters.AddRange(this.whereParameters);
|
||||
q.orderbyColumns.AddRange(this.orderbyColumns);
|
||||
q.selectColumns.AddRange(this.selectColumns);
|
||||
q.joinColumns.AddRange(this.joinColumns);
|
||||
q.projectionDelegates.AddRange(this.projectionDelegates);
|
||||
|
||||
MethodCallExpression methodCallExpression = (MethodCallExpression) expression;
|
||||
string methodName = methodCallExpression.Method.Name;
|
||||
if (methodName == "Select")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildProjection(null, argumentExpression);
|
||||
}
|
||||
else if (methodName == "Where")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildQuery(null, argumentExpression);
|
||||
}
|
||||
else if (methodName == "ThenBy")
|
||||
{
|
||||
LambdaExpression argumentExpression = (LambdaExpression)
|
||||
((UnaryExpression) methodCallExpression.Arguments[1]).Operand;
|
||||
q.BuildSequence(null, argumentExpression);
|
||||
}
|
||||
else if (methodName == "Join")
|
||||
{
|
||||
ConstantExpression constantExpression = (ConstantExpression)
|
||||
methodCallExpression.Arguments[1];
|
||||
IQueryable inner = (IQueryable) constantExpression.Value;
|
||||
q.PerformJoin(
|
||||
null,
|
||||
null,
|
||||
inner,
|
||||
GetJoinLambda(methodCallExpression.Arguments[2]),
|
||||
GetJoinLambda(methodCallExpression.Arguments[3]),
|
||||
GetJoinLambda(methodCallExpression.Arguments[4]));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Query operation not supported: " + methodName);
|
||||
}
|
||||
|
||||
return q;
|
||||
}
|
||||
|
||||
public IQueryable CreateQuery(Expression expression)
|
||||
{
|
||||
return this.CreateQuery<T>(expression);
|
||||
}
|
||||
|
||||
private static LambdaExpression GetJoinLambda(Expression expresion)
|
||||
{
|
||||
UnaryExpression unaryExpression = (UnaryExpression) expresion;
|
||||
return (LambdaExpression) unaryExpression.Operand;
|
||||
}
|
||||
|
||||
public TResult Execute<TResult>(Expression expression)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Direct method calls not supported -- use AsEnumerable() instead.");
|
||||
}
|
||||
|
||||
object IQueryProvider.Execute(Expression expression)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Direct method calls not supported -- use AsEnumerable() instead.");
|
||||
}
|
||||
|
||||
public IQueryProvider Provider
|
||||
{
|
||||
get
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public Type ElementType
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
public Expression Expression
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.queryableExpression;
|
||||
}
|
||||
}
|
||||
|
||||
internal void BuildQuery(TableInfo tableInfo, LambdaExpression expression)
|
||||
{
|
||||
if (tableInfo != null)
|
||||
{
|
||||
this.tables.Add(tableInfo);
|
||||
this.recordTypes.Add(typeof(T));
|
||||
this.selectors.Add(expression.Parameters[0].Name);
|
||||
}
|
||||
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
|
||||
this.ParseQuery(expression.Body, queryBuilder);
|
||||
|
||||
this.where = queryBuilder.ToString();
|
||||
}
|
||||
|
||||
internal void BuildNullQuery(TableInfo tableInfo, Type recordType, LambdaExpression expression)
|
||||
{
|
||||
this.tables.Add(tableInfo);
|
||||
this.recordTypes.Add(recordType);
|
||||
this.selectors.Add(expression.Parameters[0].Name);
|
||||
}
|
||||
|
||||
private void ParseQuery(Expression expression, StringBuilder queryBuilder)
|
||||
{
|
||||
queryBuilder.Append("(");
|
||||
|
||||
BinaryExpression binaryExpression;
|
||||
UnaryExpression unaryExpression;
|
||||
MethodCallExpression methodCallExpression;
|
||||
|
||||
if ((binaryExpression = expression as BinaryExpression) != null)
|
||||
{
|
||||
switch (binaryExpression.NodeType)
|
||||
{
|
||||
case ExpressionType.AndAlso:
|
||||
this.ParseQuery(binaryExpression.Left, queryBuilder);
|
||||
queryBuilder.Append(" AND ");
|
||||
this.ParseQuery(binaryExpression.Right, queryBuilder);
|
||||
break;
|
||||
|
||||
case ExpressionType.OrElse:
|
||||
this.ParseQuery(binaryExpression.Left, queryBuilder);
|
||||
queryBuilder.Append(" OR ");
|
||||
this.ParseQuery(binaryExpression.Right, queryBuilder);
|
||||
break;
|
||||
|
||||
case ExpressionType.Equal:
|
||||
case ExpressionType.NotEqual:
|
||||
case ExpressionType.GreaterThan:
|
||||
case ExpressionType.LessThan:
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
this.ParseQueryCondition(binaryExpression, queryBuilder);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"Expression type not supported: " + binaryExpression.NodeType );
|
||||
}
|
||||
}
|
||||
else if ((unaryExpression = expression as UnaryExpression) != null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Expression type not supported: " + unaryExpression.NodeType);
|
||||
}
|
||||
else if ((methodCallExpression = expression as MethodCallExpression) != null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Method call not supported: " + methodCallExpression.Method.Name + "()");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Query filter expression not supported: " + expression);
|
||||
}
|
||||
|
||||
queryBuilder.Append(")");
|
||||
}
|
||||
|
||||
private static ExpressionType OppositeExpression(ExpressionType e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case ExpressionType.LessThan:
|
||||
return ExpressionType.GreaterThan;
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
return ExpressionType.GreaterThanOrEqual;
|
||||
case ExpressionType.GreaterThan:
|
||||
return ExpressionType.LessThan;
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
return ExpressionType.LessThanOrEqual;
|
||||
default:
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsIntegerType(Type t)
|
||||
{
|
||||
return
|
||||
t == typeof(sbyte) ||
|
||||
t == typeof(byte) ||
|
||||
t == typeof(short) ||
|
||||
t == typeof(ushort) ||
|
||||
t == typeof(int) ||
|
||||
t == typeof(uint) ||
|
||||
t == typeof(long) ||
|
||||
t == typeof(ulong);
|
||||
}
|
||||
|
||||
private void ParseQueryCondition(
|
||||
BinaryExpression binaryExpression, StringBuilder queryBuilder)
|
||||
{
|
||||
bool swap;
|
||||
string column = this.GetConditionColumn(binaryExpression, out swap);
|
||||
queryBuilder.Append(column);
|
||||
|
||||
ExpressionType expressionType = binaryExpression.NodeType;
|
||||
if (swap)
|
||||
{
|
||||
expressionType = OppositeExpression(expressionType);
|
||||
}
|
||||
|
||||
LambdaExpression valueExpression = Expression.Lambda(
|
||||
swap ? binaryExpression.Left : binaryExpression.Right);
|
||||
object value = valueExpression.Compile().DynamicInvoke();
|
||||
|
||||
bool valueIsInt = false;
|
||||
if (value != null)
|
||||
{
|
||||
if (IsIntegerType(value.GetType()))
|
||||
{
|
||||
valueIsInt = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
switch (expressionType)
|
||||
{
|
||||
case ExpressionType.Equal:
|
||||
if (value == null)
|
||||
{
|
||||
queryBuilder.Append(" IS NULL");
|
||||
}
|
||||
else if (valueIsInt)
|
||||
{
|
||||
queryBuilder.Append(" = ");
|
||||
queryBuilder.Append(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
queryBuilder.Append(" = ?");
|
||||
this.whereParameters.Add(value);
|
||||
}
|
||||
return;
|
||||
|
||||
case ExpressionType.NotEqual:
|
||||
if (value == null)
|
||||
{
|
||||
queryBuilder.Append(" IS NOT NULL");
|
||||
}
|
||||
else if (valueIsInt)
|
||||
{
|
||||
queryBuilder.Append(" <> ");
|
||||
queryBuilder.Append(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
queryBuilder.Append(" <> ?");
|
||||
this.whereParameters.Add(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A null value was used in a greater-than/less-than operation.");
|
||||
}
|
||||
|
||||
if (!valueIsInt)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Greater-than/less-than operators not supported on strings.");
|
||||
}
|
||||
|
||||
switch (expressionType)
|
||||
{
|
||||
case ExpressionType.LessThan:
|
||||
queryBuilder.Append(" < ");
|
||||
break;
|
||||
|
||||
case ExpressionType.LessThanOrEqual:
|
||||
queryBuilder.Append(" <= ");
|
||||
break;
|
||||
|
||||
case ExpressionType.GreaterThan:
|
||||
queryBuilder.Append(" > ");
|
||||
break;
|
||||
|
||||
case ExpressionType.GreaterThanOrEqual:
|
||||
queryBuilder.Append(" >= ");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException(
|
||||
"Unsupported query expression type: " + expressionType);
|
||||
}
|
||||
|
||||
queryBuilder.Append(value);
|
||||
}
|
||||
|
||||
private string GetConditionColumn(
|
||||
BinaryExpression binaryExpression, out bool swap)
|
||||
{
|
||||
MemberExpression memberExpression;
|
||||
MethodCallExpression methodCallExpression;
|
||||
|
||||
if (((memberExpression = binaryExpression.Left as MemberExpression) != null) ||
|
||||
((binaryExpression.Left.NodeType == ExpressionType.Convert ||
|
||||
binaryExpression.Left.NodeType == ExpressionType.ConvertChecked) &&
|
||||
(memberExpression = ((UnaryExpression) binaryExpression.Left).Operand
|
||||
as MemberExpression) != null))
|
||||
{
|
||||
string column = this.GetConditionColumn(memberExpression);
|
||||
if (column != null)
|
||||
{
|
||||
swap = false;
|
||||
return column;
|
||||
}
|
||||
}
|
||||
else if (((memberExpression = binaryExpression.Right as MemberExpression) != null) ||
|
||||
((binaryExpression.Right.NodeType == ExpressionType.Convert ||
|
||||
binaryExpression.Right.NodeType == ExpressionType.ConvertChecked) &&
|
||||
(memberExpression = ((UnaryExpression) binaryExpression.Right).Operand
|
||||
as MemberExpression) != null))
|
||||
{
|
||||
string column = this.GetConditionColumn(memberExpression);
|
||||
if (column != null)
|
||||
{
|
||||
swap = true;
|
||||
return column;
|
||||
}
|
||||
}
|
||||
else if ((methodCallExpression = binaryExpression.Left as MethodCallExpression) != null)
|
||||
{
|
||||
string column = this.GetConditionColumn(methodCallExpression);
|
||||
if (column != null)
|
||||
{
|
||||
swap = false;
|
||||
return column;
|
||||
}
|
||||
}
|
||||
else if ((methodCallExpression = binaryExpression.Right as MethodCallExpression) != null)
|
||||
{
|
||||
string column = this.GetConditionColumn(methodCallExpression);
|
||||
if (column != null)
|
||||
{
|
||||
swap = true;
|
||||
return column;
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotSupportedException(
|
||||
"Unsupported binary expression: " + binaryExpression);
|
||||
}
|
||||
|
||||
private string GetConditionColumn(MemberExpression memberExpression)
|
||||
{
|
||||
string columnName = GetColumnName(memberExpression.Member);
|
||||
string selectorName = GetConditionSelectorName(memberExpression.Expression);
|
||||
string tableName = this.GetConditionTable(selectorName, columnName);
|
||||
return this.FormatColumn(tableName, columnName);
|
||||
}
|
||||
|
||||
private string GetConditionColumn(MethodCallExpression methodCallExpression)
|
||||
{
|
||||
LambdaExpression argumentExpression =
|
||||
Expression.Lambda(methodCallExpression.Arguments[0]);
|
||||
string columnName = (string) argumentExpression.Compile().DynamicInvoke();
|
||||
string selectorName = GetConditionSelectorName(methodCallExpression.Object);
|
||||
string tableName = this.GetConditionTable(selectorName, columnName);
|
||||
return this.FormatColumn(tableName, columnName);
|
||||
}
|
||||
|
||||
private static string GetConditionSelectorName(Expression expression)
|
||||
{
|
||||
ParameterExpression parameterExpression;
|
||||
MemberExpression memberExpression;
|
||||
if ((parameterExpression = expression as ParameterExpression) != null)
|
||||
{
|
||||
return parameterExpression.Name;
|
||||
}
|
||||
else if ((memberExpression = expression as MemberExpression) != null)
|
||||
{
|
||||
return memberExpression.Member.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Unsupported conditional selector expression: " + expression);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetConditionTable(string selectorName, string columnName)
|
||||
{
|
||||
string tableName = null;
|
||||
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
if (this.selectors[i] == selectorName)
|
||||
{
|
||||
tableName = this.tables[i].Name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tableName == null)
|
||||
{
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
|
||||
"Conditional expression contains column {0}.{1} " +
|
||||
"from a table that is not in the query.",
|
||||
selectorName,
|
||||
columnName));
|
||||
}
|
||||
|
||||
return tableName;
|
||||
}
|
||||
|
||||
private string FormatColumn(string tableName, string columnName)
|
||||
{
|
||||
if (tableName != null && this.tables.Count > 1)
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "`{0}`.`{1}`", tableName, columnName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "`{0}`", columnName);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetColumnName(MemberInfo memberInfo)
|
||||
{
|
||||
foreach (var attr in memberInfo.GetCustomAttributes(
|
||||
typeof(DatabaseColumnAttribute), false))
|
||||
{
|
||||
return ((DatabaseColumnAttribute) attr).Column;
|
||||
}
|
||||
|
||||
return memberInfo.Name;
|
||||
}
|
||||
|
||||
internal void BuildProjection(TableInfo tableInfo, LambdaExpression expression)
|
||||
{
|
||||
if (tableInfo != null)
|
||||
{
|
||||
this.tables.Add(tableInfo);
|
||||
this.recordTypes.Add(typeof(T));
|
||||
this.selectors.Add(expression.Parameters[0].Name);
|
||||
}
|
||||
|
||||
this.FindColumns(expression, this.selectColumns);
|
||||
this.projectionDelegates.Add(expression.Compile());
|
||||
}
|
||||
|
||||
internal void BuildSequence(TableInfo tableInfo, LambdaExpression expression)
|
||||
{
|
||||
if (tableInfo != null)
|
||||
{
|
||||
this.tables.Add(tableInfo);
|
||||
this.recordTypes.Add(typeof(T));
|
||||
this.selectors.Add(expression.Parameters[0].Name);
|
||||
}
|
||||
|
||||
this.FindColumns(expression.Body, this.orderbyColumns);
|
||||
}
|
||||
|
||||
private static void AddAllColumns(TableInfo tableInfo, IList<TableColumn> columnList)
|
||||
{
|
||||
foreach (ColumnInfo column in tableInfo.Columns)
|
||||
{
|
||||
columnList.Add(new TableColumn(tableInfo, column));
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
|
||||
private void FindColumns(Expression expression, IList<TableColumn> columnList)
|
||||
{
|
||||
if (expression is ParameterExpression)
|
||||
{
|
||||
ParameterExpression e = expression as ParameterExpression;
|
||||
string selector = e.Name;
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
if (this.selectors[i] == selector)
|
||||
{
|
||||
AddAllColumns(this.tables[i], columnList);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (expression.NodeType == ExpressionType.MemberAccess)
|
||||
{
|
||||
this.FindColumns(expression as MemberExpression, columnList);
|
||||
}
|
||||
else if (expression is MethodCallExpression)
|
||||
{
|
||||
this.FindColumns(expression as MethodCallExpression, columnList);
|
||||
}
|
||||
else if (expression is BinaryExpression)
|
||||
{
|
||||
BinaryExpression e = expression as BinaryExpression;
|
||||
this.FindColumns(e.Left, columnList);
|
||||
this.FindColumns(e.Right, columnList);
|
||||
}
|
||||
else if (expression is UnaryExpression)
|
||||
{
|
||||
UnaryExpression e = expression as UnaryExpression;
|
||||
this.FindColumns(e.Operand, columnList);
|
||||
}
|
||||
else if (expression is ConditionalExpression)
|
||||
{
|
||||
ConditionalExpression e = expression as ConditionalExpression;
|
||||
this.FindColumns(e.Test, columnList);
|
||||
this.FindColumns(e.IfTrue, columnList);
|
||||
this.FindColumns(e.IfFalse, columnList);
|
||||
}
|
||||
else if (expression is InvocationExpression)
|
||||
{
|
||||
InvocationExpression e = expression as InvocationExpression;
|
||||
this.FindColumns(e.Expression, columnList);
|
||||
this.FindColumns(e.Arguments, columnList);
|
||||
}
|
||||
else if (expression is LambdaExpression)
|
||||
{
|
||||
LambdaExpression e = expression as LambdaExpression;
|
||||
this.FindColumns(e.Body, columnList);
|
||||
}
|
||||
else if (expression is ListInitExpression)
|
||||
{
|
||||
ListInitExpression e = expression as ListInitExpression;
|
||||
this.FindColumns(e.NewExpression, columnList);
|
||||
foreach (ElementInit ei in e.Initializers)
|
||||
{
|
||||
this.FindColumns(ei.Arguments, columnList);
|
||||
}
|
||||
}
|
||||
else if (expression is MemberInitExpression)
|
||||
{
|
||||
MemberInitExpression e = expression as MemberInitExpression;
|
||||
this.FindColumns(e.NewExpression, columnList);
|
||||
foreach (MemberAssignment b in e.Bindings)
|
||||
{
|
||||
this.FindColumns(b.Expression, columnList);
|
||||
}
|
||||
}
|
||||
else if (expression is NewExpression)
|
||||
{
|
||||
NewExpression e = expression as NewExpression;
|
||||
this.FindColumns(e.Arguments, columnList);
|
||||
}
|
||||
else if (expression is NewArrayExpression)
|
||||
{
|
||||
NewArrayExpression e = expression as NewArrayExpression;
|
||||
this.FindColumns(e.Expressions, columnList);
|
||||
}
|
||||
else if (expression is TypeBinaryExpression)
|
||||
{
|
||||
TypeBinaryExpression e = expression as TypeBinaryExpression;
|
||||
this.FindColumns(e.Expression, columnList);
|
||||
}
|
||||
}
|
||||
|
||||
private void FindColumns(IEnumerable<Expression> expressions, IList<TableColumn> columnList)
|
||||
{
|
||||
foreach (Expression expression in expressions)
|
||||
{
|
||||
this.FindColumns(expression, columnList);
|
||||
}
|
||||
}
|
||||
|
||||
private void FindColumns(MemberExpression memberExpression, IList<TableColumn> columnList)
|
||||
{
|
||||
string selector = null;
|
||||
MemberExpression objectMemberExpression;
|
||||
ParameterExpression objectParameterExpression;
|
||||
if ((objectParameterExpression = memberExpression.Expression as
|
||||
ParameterExpression) != null)
|
||||
{
|
||||
selector = objectParameterExpression.Name;
|
||||
}
|
||||
else if ((objectMemberExpression = memberExpression.Expression as
|
||||
MemberExpression) != null)
|
||||
{
|
||||
selector = objectMemberExpression.Member.Name;
|
||||
}
|
||||
|
||||
if (selector != null)
|
||||
{
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
if (this.selectors[i] == selector)
|
||||
{
|
||||
string columnName = GetColumnName(memberExpression.Member);
|
||||
ColumnInfo column = this.tables[i].Columns[columnName];
|
||||
columnList.Add(new TableColumn(this.tables[i], column));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selector = memberExpression.Member.Name;
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
if (this.selectors[i] == selector)
|
||||
{
|
||||
AddAllColumns(this.tables[i], columnList);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FindColumns(MethodCallExpression methodCallExpression, IList<TableColumn> columnList)
|
||||
{
|
||||
if (methodCallExpression.Method.Name == "get_Item" &&
|
||||
methodCallExpression.Arguments.Count == 1 &&
|
||||
methodCallExpression.Arguments[0].Type == typeof(string))
|
||||
{
|
||||
string selector = null;
|
||||
MemberExpression objectMemberExpression;
|
||||
ParameterExpression objectParameterExpression;
|
||||
if ((objectParameterExpression = methodCallExpression.Object as ParameterExpression) != null)
|
||||
{
|
||||
selector = objectParameterExpression.Name;
|
||||
}
|
||||
else if ((objectMemberExpression = methodCallExpression.Object as MemberExpression) != null)
|
||||
{
|
||||
selector = objectMemberExpression.Member.Name;
|
||||
}
|
||||
|
||||
if (selector != null)
|
||||
{
|
||||
for (int i = 0; i < this.tables.Count; i++)
|
||||
{
|
||||
if (this.selectors[i] == selector)
|
||||
{
|
||||
LambdaExpression argumentExpression =
|
||||
Expression.Lambda(methodCallExpression.Arguments[0]);
|
||||
string columnName = (string)
|
||||
argumentExpression.Compile().DynamicInvoke();
|
||||
ColumnInfo column = this.tables[i].Columns[columnName];
|
||||
columnList.Add(new TableColumn(this.tables[i], column));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (methodCallExpression.Object != null && methodCallExpression.Object.NodeType != ExpressionType.Parameter)
|
||||
{
|
||||
this.FindColumns(methodCallExpression.Object, columnList);
|
||||
}
|
||||
}
|
||||
|
||||
internal void PerformJoin(
|
||||
TableInfo tableInfo,
|
||||
Type recordType,
|
||||
IQueryable joinTable,
|
||||
LambdaExpression outerKeySelector,
|
||||
LambdaExpression innerKeySelector,
|
||||
LambdaExpression resultSelector)
|
||||
{
|
||||
if (joinTable == null)
|
||||
{
|
||||
throw new ArgumentNullException("joinTable");
|
||||
}
|
||||
|
||||
if (tableInfo != null)
|
||||
{
|
||||
this.tables.Add(tableInfo);
|
||||
this.recordTypes.Add(recordType);
|
||||
this.selectors.Add(outerKeySelector.Parameters[0].Name);
|
||||
}
|
||||
|
||||
PropertyInfo tableInfoProp = joinTable.GetType().GetProperty("TableInfo");
|
||||
if (tableInfoProp == null)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Cannot join with object: " + joinTable.GetType().Name +
|
||||
"; join is only supported on another QTable.");
|
||||
}
|
||||
|
||||
TableInfo joinTableInfo = (TableInfo) tableInfoProp.GetValue(joinTable, null);
|
||||
if (joinTableInfo == null)
|
||||
{
|
||||
throw new InvalidOperationException("Missing join table info.");
|
||||
}
|
||||
|
||||
this.tables.Add(joinTableInfo);
|
||||
this.recordTypes.Add(joinTable.ElementType);
|
||||
this.selectors.Add(innerKeySelector.Parameters[0].Name);
|
||||
this.projectionDelegates.Add(resultSelector.Compile());
|
||||
|
||||
int joinColumnCount = this.joinColumns.Count;
|
||||
this.FindColumns(outerKeySelector.Body, this.joinColumns);
|
||||
if (this.joinColumns.Count > joinColumnCount + 1)
|
||||
{
|
||||
throw new NotSupportedException("Join operations involving " +
|
||||
"multiple columns are not supported.");
|
||||
}
|
||||
else if (this.joinColumns.Count != joinColumnCount + 1)
|
||||
{
|
||||
throw new InvalidOperationException("Bad outer key selector for join.");
|
||||
}
|
||||
|
||||
this.FindColumns(innerKeySelector.Body, this.joinColumns);
|
||||
if (this.joinColumns.Count > joinColumnCount + 2)
|
||||
{
|
||||
throw new NotSupportedException("Join operations involving " +
|
||||
"multiple columns not are supported.");
|
||||
}
|
||||
if (this.joinColumns.Count != joinColumnCount + 2)
|
||||
{
|
||||
throw new InvalidOperationException("Bad inner key selector for join.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TableColumn
|
||||
{
|
||||
public TableColumn(TableInfo table, ColumnInfo column)
|
||||
{
|
||||
this.Table = table;
|
||||
this.Column = column;
|
||||
}
|
||||
|
||||
public TableInfo Table { get; set; }
|
||||
public ColumnInfo Column { get; set; }
|
||||
}
|
||||
}
|
||||
+1202
File diff suppressed because it is too large.
Load diff
+1084
File diff suppressed because it is too large.
Load diff
+263
@@ -0,0 +1,263 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="PatchPackage.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Package
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to convenient properties and operations on a patch package (.MSP).
|
||||
/// </summary>
|
||||
internal class PatchPackage : Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new patch package object; opening the patch database in read-only mode.
|
||||
/// </summary>
|
||||
/// <param name="packagePath">Path to the patch package (.MSP)</param>
|
||||
/// <remarks>The PatchPackage object only opens the patch database in read-only mode, because
|
||||
/// transforms (sub-storages) cannot be read if the database is open in read-write mode.</remarks>
|
||||
public PatchPackage(string packagePath)
|
||||
: base(packagePath, (DatabaseOpenMode) ((int) DatabaseOpenMode.ReadOnly | 32))
|
||||
// TODO: figure out what to do about DatabaseOpenMode.Patch
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle this event to receive status messages when operations are performed on the patch package.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <c>patchPackage.Message += new InstallPackageMessageHandler(Console.WriteLine);</c>
|
||||
/// </example>
|
||||
public event InstallPackageMessageHandler Message;
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the <see cref="Message"/> event-handler.
|
||||
/// </summary>
|
||||
/// <param name="format">Message string, containing 0 or more format items</param>
|
||||
/// <param name="args">Items to be formatted</param>
|
||||
protected void LogMessage(string format, params object[] args)
|
||||
{
|
||||
if(this.Message != null)
|
||||
{
|
||||
this.Message(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the patch code (GUID) of the patch package.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The patch code is stored in the RevisionNumber field of the patch summary information.
|
||||
/// </remarks>
|
||||
public string PatchCode
|
||||
{
|
||||
get
|
||||
{
|
||||
string guids = this.SummaryInfo.RevisionNumber;
|
||||
return guids.Substring(0, guids.IndexOf('}') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of patch codes that are replaced by this patch package.
|
||||
/// </summary>
|
||||
/// <returns>Array of replaced patch codes (GUIDs)</returns>
|
||||
/// <remarks>
|
||||
/// The list of replaced patch codes is stored in the RevisionNumber field of the patch summary information.
|
||||
/// </remarks>
|
||||
public string[] GetReplacedPatchCodes()
|
||||
{
|
||||
ArrayList patchCodeList = new ArrayList();
|
||||
string guids = this.SummaryInfo.RevisionNumber;
|
||||
int thisGuid = guids.IndexOf('}') + 1;
|
||||
int nextGuid = guids.IndexOf('}', thisGuid) + 1;
|
||||
while(nextGuid > 0)
|
||||
{
|
||||
patchCodeList.Add(guids.Substring(thisGuid, (nextGuid - thisGuid)));
|
||||
thisGuid = nextGuid;
|
||||
nextGuid = guids.IndexOf('}', thisGuid) + 1;
|
||||
}
|
||||
return (string[]) patchCodeList.ToArray(typeof(string));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of product codes of products targeted by this patch package.
|
||||
/// </summary>
|
||||
/// <returns>Array of product codes (GUIDs)</returns>
|
||||
/// <remarks>
|
||||
/// The list of target product codes is stored in the Template field of the patch summary information.
|
||||
/// </remarks>
|
||||
public string[] GetTargetProductCodes()
|
||||
{
|
||||
string productList = this.SummaryInfo.Template;
|
||||
return productList.Split(';');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the names of the transforms included in the patch package.
|
||||
/// </summary>
|
||||
/// <returns>Array of transform names</returns>
|
||||
/// <remarks>
|
||||
/// The returned list does not include the "patch special transforms" that are prefixed with "#"
|
||||
/// <p>The list of transform names is stored in the LastSavedBy field of the patch summary information.</p>
|
||||
/// </remarks>
|
||||
public string[] GetTransforms()
|
||||
{
|
||||
return this.GetTransforms(false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the names of the transforms included in the patch package.
|
||||
/// </summary>
|
||||
/// <param name="includeSpecialTransforms">Specifies whether to include the
|
||||
/// "patch special transforms" that are prefixed with "#"</param>
|
||||
/// <returns>Array of transform names</returns>
|
||||
/// <remarks>
|
||||
/// The list of transform names is stored in the LastSavedBy field of the patch summary information.
|
||||
/// </remarks>
|
||||
public string[] GetTransforms(bool includeSpecialTransforms)
|
||||
{
|
||||
ArrayList transformArray = new ArrayList();
|
||||
string transformList = this.SummaryInfo.LastSavedBy;
|
||||
foreach(string transform in transformList.Split(';', ':'))
|
||||
{
|
||||
if(transform.Length != 0 && (includeSpecialTransforms || !transform.StartsWith("#", StringComparison.Ordinal)))
|
||||
{
|
||||
transformArray.Add(transform);
|
||||
}
|
||||
}
|
||||
return (string[]) transformArray.ToArray(typeof(string));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the transforms included in the patch package.
|
||||
/// </summary>
|
||||
/// <returns>Array containing information about each transform</returns>
|
||||
/// <remarks>
|
||||
/// The returned info does not include the "patch special transforms" that are prefixed with "#"
|
||||
/// </remarks>
|
||||
public TransformInfo[] GetTransformsInfo()
|
||||
{
|
||||
return this.GetTransformsInfo(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the transforms included in the patch package.
|
||||
/// </summary>
|
||||
/// <param name="includeSpecialTransforms">Specifies whether to include the
|
||||
/// "patch special transforms" that are prefixed with "#"</param>
|
||||
/// <returns>Array containing information about each transform</returns>
|
||||
public TransformInfo[] GetTransformsInfo(bool includeSpecialTransforms)
|
||||
{
|
||||
string[] transforms = this.GetTransforms(includeSpecialTransforms);
|
||||
ArrayList transformInfoArray = new ArrayList(transforms.Length);
|
||||
foreach(string transform in transforms)
|
||||
{
|
||||
transformInfoArray.Add(this.GetTransformInfo(transform));
|
||||
}
|
||||
return (TransformInfo[]) transformInfoArray.ToArray(typeof(TransformInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about a transforms included in the patch package.
|
||||
/// </summary>
|
||||
/// <param name="transform">Name of the transform to extract; this may optionally be a
|
||||
/// special transform prefixed by "#"</param>
|
||||
/// <returns>Information about the transform</returns>
|
||||
public TransformInfo GetTransformInfo(string transform)
|
||||
{
|
||||
string tempTransformFile = null;
|
||||
try
|
||||
{
|
||||
tempTransformFile = Path.GetTempFileName();
|
||||
this.ExtractTransform(transform, tempTransformFile);
|
||||
using(SummaryInfo transformSummInfo = new SummaryInfo(tempTransformFile, false))
|
||||
{
|
||||
return new TransformInfo(transform, transformSummInfo);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(tempTransformFile != null && File.Exists(tempTransformFile))
|
||||
{
|
||||
File.Delete(tempTransformFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes the transforms included in the patch package to find the ones that
|
||||
/// are applicable to an install package.
|
||||
/// </summary>
|
||||
/// <param name="installPackage">The install package to validate the transforms against</param>
|
||||
/// <returns>Array of valid transform names</returns>
|
||||
/// <remarks>
|
||||
/// The returned list does not include the "patch special transforms" that
|
||||
/// are prefixed with "#" If a transform is valid, then its corresponding
|
||||
/// special transform is assumed to be valid as well.
|
||||
/// </remarks>
|
||||
public string[] GetValidTransforms(Database installPackage)
|
||||
{
|
||||
ArrayList transformArray = new ArrayList();
|
||||
string transformList = this.SummaryInfo.LastSavedBy;
|
||||
foreach(string transform in transformList.Split(';', ':'))
|
||||
{
|
||||
if(transform.Length != 0 && !transform.StartsWith("#", StringComparison.Ordinal))
|
||||
{
|
||||
this.LogMessage("Checking validity of transform {0}", transform);
|
||||
string tempTransformFile = null;
|
||||
try
|
||||
{
|
||||
tempTransformFile = Path.GetTempFileName();
|
||||
this.ExtractTransform(transform, tempTransformFile);
|
||||
if(installPackage.IsTransformValid(tempTransformFile))
|
||||
{
|
||||
this.LogMessage("Found valid transform: {0}", transform);
|
||||
transformArray.Add(transform);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(tempTransformFile != null && File.Exists(tempTransformFile))
|
||||
{
|
||||
try { File.Delete(tempTransformFile); }
|
||||
catch(IOException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (string[]) transformArray.ToArray(typeof(string));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a transform (.MST) from a patch package.
|
||||
/// </summary>
|
||||
/// <param name="transform">Name of the transform to extract; this may optionally be a
|
||||
/// special transform prefixed by "#"</param>
|
||||
/// <param name="extractFile">Location where the transform will be extracted</param>
|
||||
public void ExtractTransform(string transform, string extractFile)
|
||||
{
|
||||
using(View stgView = this.OpenView("SELECT `Name`, `Data` FROM `_Storages` WHERE `Name` = '{0}'", transform))
|
||||
{
|
||||
stgView.Execute();
|
||||
Record stgRec = stgView.Fetch();
|
||||
if(stgRec == null)
|
||||
{
|
||||
this.LogMessage("Transform not found: {0}", transform);
|
||||
throw new InstallerException("Transform not found: " + transform);
|
||||
}
|
||||
using(stgRec)
|
||||
{
|
||||
stgRec.GetStream("Data", extractFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="TransformInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller.Package
|
||||
{
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Contains properties of a transform package (.MST).
|
||||
/// </summary>
|
||||
internal class TransformInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads transform information from a transform package.
|
||||
/// </summary>
|
||||
/// <param name="mstFile">Path to a transform package (.MST file).</param>
|
||||
public TransformInfo(string mstFile)
|
||||
{
|
||||
this.name = Path.GetFileName(mstFile);
|
||||
using (SummaryInfo transformSummInfo = new SummaryInfo(mstFile, false))
|
||||
{
|
||||
this.DecodeSummaryInfo(transformSummInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads transform information from the summary information of a transform package.
|
||||
/// </summary>
|
||||
/// <param name="name">Filename of the transform (optional).</param>
|
||||
/// <param name="transformSummaryInfo">Handle to the summary information of a transform package (.MST file).</param>
|
||||
public TransformInfo(string name, SummaryInfo transformSummaryInfo)
|
||||
{
|
||||
this.name = name;
|
||||
this.DecodeSummaryInfo(transformSummaryInfo);
|
||||
}
|
||||
|
||||
private void DecodeSummaryInfo(SummaryInfo transformSummaryInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] rev = transformSummaryInfo.RevisionNumber.Split(new char[] { ';' }, 3);
|
||||
this.targetProductCode = rev[0].Substring(0, 38);
|
||||
this.targetProductVersion = rev[0].Substring(38);
|
||||
this.upgradeProductCode = rev[1].Substring(0, 38);
|
||||
this.upgradeProductVersion = rev[1].Substring(38);
|
||||
this.upgradeCode = rev[2];
|
||||
|
||||
string[] templ = transformSummaryInfo.Template.Split(new Char[] { ';' }, 2);
|
||||
this.targetPlatform = templ[0];
|
||||
this.targetLanguage = 0;
|
||||
if (templ.Length >= 2 && templ[1].Length > 0)
|
||||
{
|
||||
this.targetLanguage = Int32.Parse(templ[1], CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
|
||||
this.validateFlags = (TransformValidations) transformSummaryInfo.CharacterCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InstallerException("Invalid transform summary info", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the filename of the transform.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
private string name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target product code of the transform.
|
||||
/// </summary>
|
||||
public string TargetProductCode
|
||||
{
|
||||
get { return this.targetProductCode; }
|
||||
}
|
||||
private string targetProductCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target product version of the transform.
|
||||
/// </summary>
|
||||
public string TargetProductVersion
|
||||
{
|
||||
get { return this.targetProductVersion; }
|
||||
}
|
||||
private string targetProductVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upgrade product code of the transform.
|
||||
/// </summary>
|
||||
public string UpgradeProductCode
|
||||
{
|
||||
get { return this.upgradeProductCode; }
|
||||
}
|
||||
private string upgradeProductCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upgrade product version of the transform.
|
||||
/// </summary>
|
||||
public string UpgradeProductVersion
|
||||
{
|
||||
get { return this.upgradeProductVersion; }
|
||||
}
|
||||
private string upgradeProductVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the upgrade code of the transform.
|
||||
/// </summary>
|
||||
public string UpgradeCode
|
||||
{
|
||||
get { return this.upgradeCode; }
|
||||
}
|
||||
private string upgradeCode;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target platform of the transform.
|
||||
/// </summary>
|
||||
public string TargetPlatform
|
||||
{
|
||||
get { return this.targetPlatform; }
|
||||
}
|
||||
private string targetPlatform;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target language of the transform, or 0 if the transform is language-neutral.
|
||||
/// </summary>
|
||||
public int TargetLanguage
|
||||
{
|
||||
get { return this.targetLanguage; }
|
||||
}
|
||||
private int targetLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validation flags specified when the transform was generated.
|
||||
/// </summary>
|
||||
public TransformValidations Validations
|
||||
{
|
||||
get { return this.validateFlags; }
|
||||
}
|
||||
private TransformValidations validateFlags;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of the transform.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return (this.Name != null ? this.Name : "MST");
|
||||
}
|
||||
}
|
||||
}
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ColumnCollection.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of column information related to a <see cref="TableInfo"/> or
|
||||
/// <see cref="View"/>.
|
||||
/// </summary>
|
||||
internal sealed class ColumnCollection : ICollection<ColumnInfo>
|
||||
{
|
||||
private IList<ColumnInfo> columns;
|
||||
private string formatString;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ColumnCollection based on a specified list of columns.
|
||||
/// </summary>
|
||||
/// <param name="columns">columns to be added to the new collection</param>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public ColumnCollection(ICollection<ColumnInfo> columns)
|
||||
{
|
||||
if (columns == null)
|
||||
{
|
||||
throw new ArgumentNullException("columns");
|
||||
}
|
||||
|
||||
this.columns = new List<ColumnInfo>(columns);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ColumnCollection that is associated with a database table.
|
||||
/// </summary>
|
||||
/// <param name="view">view that contains the columns</param>
|
||||
internal ColumnCollection(View view)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
throw new ArgumentNullException("view");
|
||||
}
|
||||
|
||||
this.columns = ColumnCollection.GetViewColumns(view);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of columns in the collection.
|
||||
/// </summary>
|
||||
/// <value>number of columns in the collection</value>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.columns.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean value indicating whether the collection is read-only.
|
||||
/// A ColumnCollection is read-only if it is associated with a <see cref="View"/>
|
||||
/// or a read-only <see cref="Database"/>.
|
||||
/// </summary>
|
||||
/// <value>read-only status of the collection</value>
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about a specific column in the collection.
|
||||
/// </summary>
|
||||
/// <param name="columnIndex">1-based index into the column collection</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="columnIndex"/> is less
|
||||
/// than 1 or greater than the number of columns in the collection</exception>
|
||||
public ColumnInfo this[int columnIndex]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (columnIndex >= 0 && columnIndex < this.columns.Count)
|
||||
{
|
||||
return this.columns[columnIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("columnIndex");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about a specific column in the collection.
|
||||
/// </summary>
|
||||
/// <param name="columnName">case-sensitive name of a column collection</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><paramref name="columnName"/> does
|
||||
/// not exist in the collection</exception>
|
||||
public ColumnInfo this[string columnName]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(columnName))
|
||||
{
|
||||
throw new ArgumentNullException("columnName");
|
||||
}
|
||||
|
||||
foreach (ColumnInfo colInfo in this.columns)
|
||||
{
|
||||
if (colInfo.Name == columnName)
|
||||
{
|
||||
return colInfo;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException("columnName");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not supported because the collection is read-only.
|
||||
/// </summary>
|
||||
/// <param name="item">information about the column being added</param>
|
||||
/// <exception cref="InvalidOperationException">the collection is read-only</exception>
|
||||
public void Add(ColumnInfo item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not supported because the collection is read-only.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">the collection is read-only</exception>
|
||||
public void Clear()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a column with a given name exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="columnName">case-sensitive name of the column to look for</param>
|
||||
/// <returns>true if the column exists in the collection, false otherwise</returns>
|
||||
public bool Contains(string columnName)
|
||||
{
|
||||
return this.IndexOf(columnName) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a column with a given name exists in the collection.
|
||||
/// </summary>
|
||||
/// <param name="column">column to look for, with case-sensitive name</param>
|
||||
/// <returns>true if the column exists in the collection, false otherwise</returns>
|
||||
bool ICollection<ColumnInfo>.Contains(ColumnInfo column)
|
||||
{
|
||||
return this.Contains(column.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of a column within the collection.
|
||||
/// </summary>
|
||||
/// <param name="columnName">case-sensitive name of the column to look for</param>
|
||||
/// <returns>0-based index of the column, or -1 if not found</returns>
|
||||
public int IndexOf(string columnName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(columnName))
|
||||
{
|
||||
throw new ArgumentNullException("columnName");
|
||||
}
|
||||
|
||||
for (int index = 0; index < this.columns.Count; index++)
|
||||
{
|
||||
if (this.columns[index].Name == columnName)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the columns from this collection into an array.
|
||||
/// </summary>
|
||||
/// <param name="array">destination array to be filed</param>
|
||||
/// <param name="arrayIndex">offset into the destination array where copying begins</param>
|
||||
public void CopyTo(ColumnInfo[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
|
||||
this.columns.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Not supported because the collection is read-only.
|
||||
/// </summary>
|
||||
/// <param name="column">column to remove</param>
|
||||
/// <returns>true if the column was removed, false if it was not found</returns>
|
||||
/// <exception cref="InvalidOperationException">the collection is read-only</exception>
|
||||
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "column")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
bool ICollection<ColumnInfo>.Remove(ColumnInfo column)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator over the columns in the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator of ColumnInfo objects.</returns>
|
||||
public IEnumerator<ColumnInfo> GetEnumerator()
|
||||
{
|
||||
return this.columns.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string suitable for printing all the values of a record containing these columns.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string FormatString
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.formatString == null)
|
||||
{
|
||||
this.formatString = CreateFormatString(this.columns);
|
||||
}
|
||||
return this.formatString;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
private static string CreateFormatString(IList<ColumnInfo> columns)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
if (columns[i].Type == typeof(Stream))
|
||||
{
|
||||
sb.AppendFormat("{0} = [Binary Data]", columns[i].Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendFormat("{0} = [{1}]", columns[i].Name, i + 1);
|
||||
}
|
||||
|
||||
if (i < columns.Count - 1)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator over the columns in the collection.
|
||||
/// </summary>
|
||||
/// <returns>An enumerator of ColumnInfo objects.</returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ColumnInfo objects for the associated view.
|
||||
/// </summary>
|
||||
/// <returns>dynamically-generated list of columns</returns>
|
||||
private static IList<ColumnInfo> GetViewColumns(View view)
|
||||
{
|
||||
IList<string> columnNames = ColumnCollection.GetViewColumns(view, false);
|
||||
IList<string> columnTypes = ColumnCollection.GetViewColumns(view, true);
|
||||
|
||||
int count = columnNames.Count;
|
||||
if (columnTypes[count - 1] == "O0")
|
||||
{
|
||||
// Weird.. the "_Tables" table returns a second column with type "O0" -- ignore it.
|
||||
count--;
|
||||
}
|
||||
|
||||
IList<ColumnInfo> columnsList = new List<ColumnInfo>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
columnsList.Add(new ColumnInfo(columnNames[i], columnTypes[i]));
|
||||
}
|
||||
|
||||
return columnsList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of column names or column-definition-strings for the
|
||||
/// associated view.
|
||||
/// </summary>
|
||||
/// <param name="view">the view to that defines the columns</param>
|
||||
/// <param name="types">true to return types (column definition strings),
|
||||
/// false to return names</param>
|
||||
/// <returns>list of column names or types</returns>
|
||||
private static IList<string> GetViewColumns(View view, bool types)
|
||||
{
|
||||
int recordHandle;
|
||||
int typesFlag = types ? 1 : 0;
|
||||
uint ret = RemotableNativeMethods.MsiViewGetColumnInfo(
|
||||
(int) view.Handle, (uint) typesFlag, out recordHandle);
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
|
||||
using (Record rec = new Record((IntPtr) recordHandle, true, null))
|
||||
{
|
||||
int count = rec.FieldCount;
|
||||
IList<string> columnsList = new List<string>(count);
|
||||
|
||||
// Since we must be getting all strings of limited length,
|
||||
// this code is faster than calling rec.GetString(field).
|
||||
for (int field = 1; field <= count; field++)
|
||||
{
|
||||
uint bufSize = 256;
|
||||
StringBuilder buf = new StringBuilder((int) bufSize);
|
||||
ret = RemotableNativeMethods.MsiRecordGetString((int) rec.Handle, (uint) field, buf, ref bufSize);
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
columnsList.Add(buf.ToString());
|
||||
}
|
||||
return columnsList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+696
@@ -0,0 +1,696 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ColumnEnums.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
// Enumerations are in alphabetical order.
|
||||
|
||||
/// <summary>
|
||||
/// Available values for the Attributes column of the Component table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ComponentAttributes : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Local only - Component cannot be run from source.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Set this value for all components belonging to a feature to prevent the feature from being run-from-network or
|
||||
/// run-from-source. Note that if a feature has no components, the feature always shows run-from-source and
|
||||
/// run-from-my-computer as valid options.
|
||||
/// </p></remarks>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// Component can only be run from source.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Set this bit for all components belonging to a feature to prevent the feature from being run-from-my-computer.
|
||||
/// Note that if a feature has no components, the feature always shows run-from-source and run-from-my-computer
|
||||
/// as valid options.
|
||||
/// </p></remarks>
|
||||
SourceOnly = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Component can run locally or from source.
|
||||
/// </summary>
|
||||
Optional = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the value in the KeyPath column is used as a key into the Registry table.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// If the Value field of the corresponding record in the Registry table is null, the Name field in that record
|
||||
/// must not contain "+", "-", or "*". For more information, see the description of the Name field in Registry
|
||||
/// table.
|
||||
/// <p>Setting this bit is recommended for registry entries written to the HKCU hive. This ensures the installer
|
||||
/// writes the necessary HKCU registry entries when there are multiple users on the same machine.</p>
|
||||
/// </p></remarks>
|
||||
RegistryKeyPath = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the installer increments the reference count in the shared DLL registry of the component's
|
||||
/// key file. If this bit is not set, the installer increments the reference count only if the reference count
|
||||
/// already exists.
|
||||
/// </summary>
|
||||
SharedDllRefCount = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the installer does not remove the component during an uninstall. The installer registers
|
||||
/// an extra system client for the component in the Windows Installer registry settings.
|
||||
/// </summary>
|
||||
Permanent = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the value in the KeyPath column is a key into the ODBCDataSource table.
|
||||
/// </summary>
|
||||
OdbcDataSource = 0x0020,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the installer reevaluates the value of the statement in the Condition column upon a reinstall.
|
||||
/// If the value was previously False and has changed to true, the installer installs the component. If the value
|
||||
/// was previously true and has changed to false, the installer removes the component even if the component has
|
||||
/// other products as clients.
|
||||
/// </summary>
|
||||
Transitive = 0x0040,
|
||||
|
||||
/// <summary>
|
||||
/// If this bit is set, the installer does not install or reinstall the component if a key path file or a key path
|
||||
/// registry entry for the component already exists. The application does register itself as a client of the component.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Use this flag only for components that are being registered by the Registry table. Do not use this flag for
|
||||
/// components registered by the AppId, Class, Extension, ProgId, MIME, and Verb tables.
|
||||
/// </p></remarks>
|
||||
NeverOverwrite = 0x0080,
|
||||
|
||||
/// <summary>
|
||||
/// Set this bit to mark this as a 64-bit component. This attribute facilitates the installation of packages that
|
||||
/// include both 32-bit and 64-bit components. If this bit is not set, the component is registered as a 32-bit component.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// If this is a 64-bit component replacing a 32-bit component, set this bit and assign a new GUID in the
|
||||
/// ComponentId column.
|
||||
/// </p></remarks>
|
||||
SixtyFourBit = 0x0100,
|
||||
|
||||
/// <summary>
|
||||
/// Set this bit to disable registry reflection on all existing and new registry keys affected by this component.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// If this bit is set, the Windows Installer calls the RegDisableReflectionKey on each key being accessed by the component.
|
||||
/// This bit is available with Windows Installer version 4.0 and is ignored on 32-bit systems.
|
||||
/// </p></remarks>
|
||||
DisableRegistryReflection = 0x0200,
|
||||
|
||||
/// <summary>
|
||||
/// [MSI 4.5] Set this bit for a component in a patch package to prevent leaving orphan components on the computer.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// If a subsequent patch is installed, marked with the SupersedeEarlier flag in its MsiPatchSequence
|
||||
/// table to supersede the first patch, Windows Installer 4.5 can unregister and uninstall components marked with the
|
||||
/// UninstallOnSupersedence value. If the component is not marked with this bit, installation of a superseding patch can leave
|
||||
/// behind an unused component on the computer.
|
||||
/// </p></remarks>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Supersedence")]
|
||||
UninstallOnSupersedence = 0x0400,
|
||||
|
||||
/// <summary>
|
||||
/// [MSI 4.5] If a component is marked with this attribute value in at least one package installed on the system,
|
||||
/// the installer treats the component as marked in all packages. If a package that shares the marked component
|
||||
/// is uninstalled, Windows Installer 4.5 can continue to share the highest version of the component on the system,
|
||||
/// even if that highest version was installed by the package that is being uninstalled.
|
||||
/// </summary>
|
||||
Shared = 0x0800,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines flags for the Attributes column of the Control table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ControlAttributes : int
|
||||
{
|
||||
/// <summary>If this bit is set, the control is visible on the dialog box.</summary>
|
||||
Visible = 0x00000001,
|
||||
|
||||
/// <summary>specifies if the given control is enabled or disabled. Most controls appear gray when disabled.</summary>
|
||||
Enabled = 0x00000002,
|
||||
|
||||
/// <summary>If this bit is set, the control is displayed with a sunken, three dimensional look.</summary>
|
||||
Sunken = 0x00000004,
|
||||
|
||||
/// <summary>The Indirect control attribute specifies whether the value displayed or changed by this control is referenced indirectly.</summary>
|
||||
Indirect = 0x00000008,
|
||||
|
||||
/// <summary>If this bit is set on a control, the associated property specified in the Property column of the Control table is an integer.</summary>
|
||||
Integer = 0x00000010,
|
||||
|
||||
/// <summary>If this bit is set the text in the control is displayed in a right-to-left reading order.</summary>
|
||||
RightToLeftReadingOrder = 0x00000020,
|
||||
|
||||
/// <summary>If this style bit is set, text in the control is aligned to the right.</summary>
|
||||
RightAligned = 0x00000040,
|
||||
|
||||
/// <summary>If this bit is set, the scroll bar is located on the left side of the control, otherwise it is on the right.</summary>
|
||||
LeftScroll = 0x00000080,
|
||||
|
||||
/// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and LeftScroll attributes.</summary>
|
||||
Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
|
||||
|
||||
/// <summary>If this bit is set on a text control, the control is displayed transparently with the background showing through the control where there are no characters.</summary>
|
||||
Transparent = 0x00010000,
|
||||
|
||||
/// <summary>If this bit is set on a text control, the occurrence of the character "&" in a text string is displayed as itself.</summary>
|
||||
NoPrefix = 0x00020000,
|
||||
|
||||
/// <summary>If this bit is set the text in the control is displayed on a single line.</summary>
|
||||
NoWrap = 0x00040000,
|
||||
|
||||
/// <summary>If this bit is set for a text control, the control will automatically attempt to format the displayed text as a number representing a count of bytes.</summary>
|
||||
FormatSize = 0x00080000,
|
||||
|
||||
/// <summary>If this bit is set, fonts are created using the user's default UI code page. Otherwise it is created using the database code page.</summary>
|
||||
UsersLanguage = 0x00100000,
|
||||
|
||||
/// <summary>If this bit is set on an Edit control, the installer creates a multiple line edit control with a vertical scroll bar.</summary>
|
||||
Multiline = 0x00010000,
|
||||
|
||||
/// <summary>This attribute creates an edit control for entering passwords. The control displays each character as an asterisk (*) as they are typed into the control.</summary>
|
||||
PasswordInput = 0x00200000,
|
||||
|
||||
/// <summary>If this bit is set on a ProgressBar control, the bar is drawn as a series of small rectangles in Microsoft Windows 95-style. Otherwise it is drawn as a single continuous rectangle.</summary>
|
||||
Progress95 = 0x00010000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows removable volumes.</summary>
|
||||
RemovableVolume = 0x00010000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows fixed internal hard drives.</summary>
|
||||
FixedVolume = 0x00020000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows remote volumes.</summary>
|
||||
RemoteVolume = 0x00040000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows CD-ROM volumes.</summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cdrom")]
|
||||
CdromVolume = 0x00080000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows RAM disk volumes.</summary>
|
||||
RamDiskVolume = 0x00100000,
|
||||
|
||||
/// <summary>If this bit is set, the control shows floppy volumes.</summary>
|
||||
FloppyVolume = 0x00200000,
|
||||
|
||||
/// <summary>Specifies whether or not the rollback backup files are included in the costs displayed by the VolumeCostList control.</summary>
|
||||
ShowRollbackCost = 0x00400000,
|
||||
|
||||
/// <summary>If this bit is set, the items listed in the control are displayed in a specified order. Otherwise, items are displayed in alphabetical order.</summary>
|
||||
Sorted = 0x00010000,
|
||||
|
||||
/// <summary>If this bit is set on a combo box, the edit field is replaced by a static text field. This prevents a user from entering a new value and requires the user to choose only one of the predefined values.</summary>
|
||||
ComboList = 0x00020000,
|
||||
|
||||
//ImageHandle = 0x00010000,
|
||||
|
||||
/// <summary>If this bit is set on a check box or a radio button group, the button is drawn with the appearance of a push button, but its logic stays the same.</summary>
|
||||
PushLike = 0x00020000,
|
||||
|
||||
/// <summary>If this bit is set, the text in the control is replaced by a bitmap image. The Text column in the Control table is a foreign key into the Binary table.</summary>
|
||||
Bitmap = 0x00040000,
|
||||
|
||||
/// <summary>If this bit is set, text is replaced by an icon image and the Text column in the Control table is a foreign key into the Binary table.</summary>
|
||||
Icon = 0x00080000,
|
||||
|
||||
/// <summary>If this bit is set, the picture is cropped or centered in the control without changing its shape or size.</summary>
|
||||
FixedSize = 0x00100000,
|
||||
|
||||
/// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
|
||||
IconSize16 = 0x00200000,
|
||||
|
||||
/// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
|
||||
IconSize32 = 0x00400000,
|
||||
|
||||
/// <summary>Specifies which size of the icon image to load. If none of the bits are set, the first image is loaded.</summary>
|
||||
IconSize48 = 0x00600000,
|
||||
|
||||
/// <summary>If this bit is set, and the installation is not yet running with elevated privileges, the control is created with a UAC icon.</summary>
|
||||
ElevationShield = 0x00800000,
|
||||
|
||||
/// <summary>If this bit is set, the RadioButtonGroup has text and a border displayed around it.</summary>
|
||||
HasBorder = 0x01000000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines flags for the Type column of the CustomAction table.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Usage", "CA2217:DoNotMarkEnumsWithFlags")]
|
||||
[Flags]
|
||||
public enum CustomActionTypes : int
|
||||
{
|
||||
/// <summary>Unspecified custom action type.</summary>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>Target = entry point name</summary>
|
||||
Dll = 0x0001,
|
||||
|
||||
/// <summary>Target = command line args</summary>
|
||||
Exe = 0x0002,
|
||||
|
||||
/// <summary>Target = text string to be formatted and set into property</summary>
|
||||
TextData = 0x0003,
|
||||
|
||||
/// <summary>Target = entry point name, null if none to call</summary>
|
||||
JScript = 0x0005,
|
||||
|
||||
/// <summary>Target = entry point name, null if none to call</summary>
|
||||
VBScript = 0x0006,
|
||||
|
||||
/// <summary>Target = property list for nested engine initialization</summary>
|
||||
Install = 0x0007,
|
||||
|
||||
/// <summary>Source = File.File, file part of installation</summary>
|
||||
SourceFile = 0x0010,
|
||||
|
||||
/// <summary>Source = Directory.Directory, folder containing existing file</summary>
|
||||
Directory = 0x0020,
|
||||
|
||||
/// <summary>Source = Property.Property, full path to executable</summary>
|
||||
Property = 0x0030,
|
||||
|
||||
/// <summary>Ignore action return status, continue running</summary>
|
||||
Continue = 0x0040,
|
||||
|
||||
/// <summary>Run asynchronously</summary>
|
||||
Async = 0x0080,
|
||||
|
||||
/// <summary>Skip if UI sequence already run</summary>
|
||||
FirstSequence = 0x0100,
|
||||
|
||||
/// <summary>Skip if UI sequence already run in same process</summary>
|
||||
OncePerProcess = 0x0200,
|
||||
|
||||
/// <summary>Run on client only if UI already run on client</summary>
|
||||
ClientRepeat = 0x0300,
|
||||
|
||||
/// <summary>Queue for execution within script</summary>
|
||||
InScript = 0x0400,
|
||||
|
||||
/// <summary>In conjunction with InScript: queue in Rollback script</summary>
|
||||
Rollback = 0x0100,
|
||||
|
||||
/// <summary>In conjunction with InScript: run Commit ops from script on success</summary>
|
||||
Commit = 0x0200,
|
||||
|
||||
/// <summary>No impersonation, run in system context</summary>
|
||||
NoImpersonate = 0x0800,
|
||||
|
||||
/// <summary>Impersonate for per-machine installs on TS machines</summary>
|
||||
TSAware = 0x4000,
|
||||
|
||||
/// <summary>Script requires 64bit process</summary>
|
||||
SixtyFourBitScript = 0x1000,
|
||||
|
||||
/// <summary>Don't record the contents of the Target field in the log file</summary>
|
||||
HideTarget = 0x2000,
|
||||
|
||||
/// <summary>The custom action runs only when a patch is being uninstalled</summary>
|
||||
PatchUninstall = 0x8000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines flags for the Attributes column of the Dialog table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum DialogAttributes : int
|
||||
{
|
||||
/// <summary>If this bit is set, the dialog is originally created as visible, otherwise it is hidden.</summary>
|
||||
Visible = 0x00000001,
|
||||
|
||||
/// <summary>If this bit is set, the dialog box is modal, other dialogs of the same application cannot be put on top of it, and the dialog keeps the control while it is running.</summary>
|
||||
Modal = 0x00000002,
|
||||
|
||||
/// <summary>If this bit is set, the dialog box can be minimized. This bit is ignored for modal dialog boxes, which cannot be minimized.</summary>
|
||||
Minimize = 0x00000004,
|
||||
|
||||
/// <summary>If this style bit is set, the dialog box will stop all other applications and no other applications can take the focus.</summary>
|
||||
SysModal = 0x00000008,
|
||||
|
||||
/// <summary>If this bit is set, the other dialogs stay alive when this dialog box is created.</summary>
|
||||
KeepModeless = 0x00000010,
|
||||
|
||||
/// <summary>If this bit is set, the dialog box periodically calls the installer. If the property changes, it notifies the controls on the dialog.</summary>
|
||||
TrackDiskSpace = 0x00000020,
|
||||
|
||||
/// <summary>If this bit is set, the pictures on the dialog box are created with the custom palette (one per dialog received from the first control created).</summary>
|
||||
UseCustomPalette = 0x00000040,
|
||||
|
||||
/// <summary>If this style bit is set the text in the dialog box is displayed in right-to-left-reading order.</summary>
|
||||
RightToLeftReadingOrder = 0x00000080,
|
||||
|
||||
/// <summary>If this style bit is set, the text is aligned on the right side of the dialog box.</summary>
|
||||
RightAligned = 0x00000100,
|
||||
|
||||
/// <summary>If this style bit is set, the scroll bar is located on the left side of the dialog box.</summary>
|
||||
LeftScroll = 0x00000200,
|
||||
|
||||
/// <summary>This is a combination of the RightToLeftReadingOrder, RightAligned, and the LeftScroll dialog style bits.</summary>
|
||||
Bidirectional = RightToLeftReadingOrder | RightAligned | LeftScroll,
|
||||
|
||||
/// <summary>If this bit is set, the dialog box is an error dialog.</summary>
|
||||
Error = 0x00010000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available values for the Attributes column of the Feature table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FeatureAttributes : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Favor local - Components of this feature that are not marked for installation from source are installed locally.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
|
||||
/// is installed locally. Components marked <see cref="ComponentAttributes.SourceOnly"/> in the Component
|
||||
/// table are always run from the source CD/server. The bits FavorLocal and FavorSource work with features not
|
||||
/// listed by the ADVERTISE property.
|
||||
/// </p></remarks>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>
|
||||
/// Components of this feature not marked for local installation are installed to run from the source
|
||||
/// CD-ROM or server.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// A component shared by two or more features, some of which are set to FavorLocal and some to FavorSource,
|
||||
/// is installed to run locally. Components marked <see cref="ComponentAttributes.None"/> (local-only) in the
|
||||
/// Component table are always installed locally. The bits FavorLocal and FavorSource work with features
|
||||
/// not listed by the ADVERTISE property.
|
||||
/// </p></remarks>
|
||||
FavorSource = 0x0001,
|
||||
|
||||
/// <summary>
|
||||
/// Set this attribute and the state of the feature is the same as the state of the feature's parent.
|
||||
/// You cannot use this option if the feature is located at the root of a feature tree.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Omit this attribute and the feature state is determined according to DisallowAdvertise and
|
||||
/// FavorLocal and FavorSource.
|
||||
/// <p>To guarantee that the child feature's state always follows the state of its parent, even when the
|
||||
/// child and parent are initially set to absent in the SelectionTree control, you must include both
|
||||
/// FollowParent and UIDisallowAbsent in the attributes of the child feature.</p>
|
||||
/// <p>Note that if you set FollowParent without setting UIDisallowAbsent, the installer cannot force
|
||||
/// the child feature out of the absent state. In this case, the child feature matches the parent's
|
||||
/// installation state only if the child is set to something other than absent.</p>
|
||||
/// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
|
||||
/// </p></remarks>
|
||||
FollowParent = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// Set this attribute and the feature state is Advertise.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// If the feature is listed by the ADDDEFAULT property this bit is ignored and the feature state is determined
|
||||
/// according to FavorLocal and FavorSource.
|
||||
/// <p>Omit this attribute and the feature state is determined according to DisallowAdvertise and FavorLocal
|
||||
/// and FavorSource.</p>
|
||||
/// </p></remarks>
|
||||
FavorAdvertise = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// Set this attribute to prevent the feature from being advertised.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Note that this bit works only with features that are listed by the ADVERTISE property.
|
||||
/// <p>Set this attribute and if the listed feature is not a parent or child, the feature is installed according to
|
||||
/// FavorLocal and FavorSource.</p>
|
||||
/// <p>Set this attribute for the parent of a listed feature and the parent is installed.</p>
|
||||
/// <p>Set this attribute for the child of a listed feature and the state of the child is Absent.</p>
|
||||
/// <p>Omit this attribute and if the listed feature is not a parent or child, the feature state is Advertise.</p>
|
||||
/// <p>Omit this attribute and if the listed feature is a parent or child, the state of both features is Advertise.</p>
|
||||
/// </p></remarks>
|
||||
DisallowAdvertise = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// Set this attribute and the user interface does not display an option to change the feature state
|
||||
/// to Absent. Setting this attribute forces the feature to the installation state, whether or not the
|
||||
/// feature is visible in the UI.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Omit this attribute and the user interface displays an option to change the feature state to Absent.
|
||||
/// <p>Set FollowParent and UIDisallowAbsent to ensure a child feature follows the state of the parent feature.</p>
|
||||
/// <p>Setting this attribute not only affects the UI, but also forces the feature to the install state whether
|
||||
/// the feature is visible in the UI or not.</p>
|
||||
/// </p></remarks>
|
||||
UIDisallowAbsent = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// Set this attribute and advertising is disabled for the feature if the operating system shell does not
|
||||
/// support Windows Installer descriptors.
|
||||
/// </summary>
|
||||
NoUnsupportedAdvertise = 0x0020,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Available values for the Attributes column of the File table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum FileAttributes : int
|
||||
{
|
||||
/// <summary>No attributes.</summary>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>Read-only.</summary>
|
||||
ReadOnly = 0x0001,
|
||||
|
||||
/// <summary>Hidden.</summary>
|
||||
Hidden = 0x0002,
|
||||
|
||||
/// <summary>System.</summary>
|
||||
System = 0x0004,
|
||||
|
||||
/// <summary>The file is vital for the proper operation of the component to which it belongs.</summary>
|
||||
Vital = 0x0200,
|
||||
|
||||
/// <summary>The file contains a valid checksum. A checksum is required to repair a file that has become corrupted.</summary>
|
||||
Checksum = 0x0400,
|
||||
|
||||
/// <summary>This bit must only be added by a patch and if the file is being added by the patch.</summary>
|
||||
PatchAdded = 0x1000,
|
||||
|
||||
/// <summary>
|
||||
/// The file's source type is uncompressed. If set, ignore the WordCount summary information property. If neither
|
||||
/// Noncompressed nor Compressed are set, the compression state of the file is specified by the WordCount summary
|
||||
/// information property. Do not set both Noncompressed and Compressed.
|
||||
/// </summary>
|
||||
NonCompressed = 0x2000,
|
||||
|
||||
/// <summary>
|
||||
/// The file's source type is compressed. If set, ignore the WordCount summary information property. If neither
|
||||
/// Noncompressed or Compressed are set, the compression state of the file is specified by the WordCount summary
|
||||
/// information property. Do not set both Noncompressed and Compressed.
|
||||
/// </summary>
|
||||
Compressed = 0x4000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the Action column of the IniFile and RemoveIniFile tables.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ini")]
|
||||
public enum IniFileAction : int
|
||||
{
|
||||
/// <summary>Creates or updates a .ini entry.</summary>
|
||||
AddLine = 0,
|
||||
|
||||
/// <summary>Creates a .ini entry only if the entry does not already exist.</summary>
|
||||
CreateLine = 1,
|
||||
|
||||
/// <summary>Deletes .ini entry.</summary>
|
||||
RemoveLine = 2,
|
||||
|
||||
/// <summary>Creates a new entry or appends a new comma-separated value to an existing entry.</summary>
|
||||
AddTag = 3,
|
||||
|
||||
/// <summary>Deletes a tag from a .ini entry.</summary>
|
||||
RemoveTag = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the Type column of the CompLocator, IniLocator, and RegLocator tables.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
[SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")]
|
||||
public enum LocatorTypes : int
|
||||
{
|
||||
/// <summary>Key path is a directory.</summary>
|
||||
Directory = 0x00000000,
|
||||
|
||||
/// <summary>Key path is a file name.</summary>
|
||||
FileName = 0x00000001,
|
||||
|
||||
/// <summary>Key path is a registry value.</summary>
|
||||
RawValue = 0x00000002,
|
||||
|
||||
/// <summary>Set this bit to have the installer search the 64-bit portion of the registry.</summary>
|
||||
SixtyFourBit = 0x00000010,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the Root column of the Registry, RemoveRegistry, and RegLocator tables.
|
||||
/// </summary>
|
||||
public enum RegistryRoot : int
|
||||
{
|
||||
/// <summary>HKEY_CURRENT_USER for a per-user installation,
|
||||
/// or HKEY_LOCAL_MACHINE for a per-machine installation.</summary>
|
||||
UserOrMachine = -1,
|
||||
|
||||
/// <summary>HKEY_CLASSES_ROOT</summary>
|
||||
ClassesRoot = 0,
|
||||
|
||||
/// <summary>HKEY_CURRENT_USER</summary>
|
||||
CurrentUser = 1,
|
||||
|
||||
/// <summary>HKEY_LOCAL_MACHINE</summary>
|
||||
LocalMachine = 2,
|
||||
|
||||
/// <summary>HKEY_USERS</summary>
|
||||
Users = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the InstallMode column of the RemoveFile table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RemoveFileModes : int
|
||||
{
|
||||
/// <summary>Never remove.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Remove when the associated component is being installed (install state = local or source).</summary>
|
||||
OnInstall = 1,
|
||||
|
||||
/// <summary>Remove when the associated component is being removed (install state = absent).</summary>
|
||||
OnRemove = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the ServiceType, StartType, and ErrorControl columns of the ServiceInstall table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ServiceAttributes : int
|
||||
{
|
||||
/// <summary>No flags.</summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>A Win32 service that runs its own process.</summary>
|
||||
OwnProcess = 0x0010,
|
||||
|
||||
/// <summary>A Win32 service that shares a process.</summary>
|
||||
ShareProcess = 0x0020,
|
||||
|
||||
/// <summary>A Win32 service that interacts with the desktop.
|
||||
/// This value cannot be used alone and must be added to either
|
||||
/// <see cref="OwnProcess"/> or <see cref="ShareProcess"/>.</summary>
|
||||
Interactive = 0x0100,
|
||||
|
||||
/// <summary>Service starts during startup of the system.</summary>
|
||||
AutoStart = 0x0002,
|
||||
|
||||
/// <summary>Service starts when the service control manager calls the StartService function.</summary>
|
||||
DemandStart = 0x0003,
|
||||
|
||||
/// <summary>Specifies a service that can no longer be started.</summary>
|
||||
Disabled = 0x0004,
|
||||
|
||||
/// <summary>Logs the error, displays a message box and continues the startup operation.</summary>
|
||||
ErrorMessage = 0x0001,
|
||||
|
||||
/// <summary>Logs the error if it is possible and the system is restarted with the last configuration
|
||||
/// known to be good. If the last-known-good configuration is being started, the startup operation fails.</summary>
|
||||
ErrorCritical = 0x0003,
|
||||
|
||||
/// <summary>When combined with other error flags, specifies that the overall install should fail if
|
||||
/// the service cannot be installed into the system.</summary>
|
||||
ErrorControlVital = 0x8000,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the Event column of the ServiceControl table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ServiceControlEvents : int
|
||||
{
|
||||
/// <summary>No control events.</summary>
|
||||
None = 0x0000,
|
||||
|
||||
/// <summary>During an install, starts the service during the StartServices action.</summary>
|
||||
Start = 0x0001,
|
||||
|
||||
/// <summary>During an install, stops the service during the StopServices action.</summary>
|
||||
Stop = 0x0002,
|
||||
|
||||
/// <summary>During an install, deletes the service during the DeleteServices action.</summary>
|
||||
Delete = 0x0008,
|
||||
|
||||
/// <summary>During an uninstall, starts the service during the StartServices action.</summary>
|
||||
UninstallStart = 0x0010,
|
||||
|
||||
/// <summary>During an uninstall, stops the service during the StopServices action.</summary>
|
||||
UninstallStop = 0x0020,
|
||||
|
||||
/// <summary>During an uninstall, deletes the service during the DeleteServices action.</summary>
|
||||
UninstallDelete = 0x0080,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the StyleBits column of the TextStyle table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TextStyles : int
|
||||
{
|
||||
/// <summary>Bold</summary>
|
||||
Bold = 0x0001,
|
||||
|
||||
/// <summary>Italic</summary>
|
||||
Italic = 0x0002,
|
||||
|
||||
/// <summary>Underline</summary>
|
||||
Underline = 0x0004,
|
||||
|
||||
/// <summary>Strike out</summary>
|
||||
Strike = 0x0008,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines values for the Attributes column of the Upgrade table.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum UpgradeAttributes : int
|
||||
{
|
||||
/// <summary>Migrates feature states by enabling the logic in the MigrateFeatureStates action.</summary>
|
||||
MigrateFeatures = 0x0001,
|
||||
|
||||
/// <summary>Detects products and applications but does not remove.</summary>
|
||||
OnlyDetect = 0x0002,
|
||||
|
||||
/// <summary>Continues installation upon failure to remove a product or application.</summary>
|
||||
IgnoreRemoveFailure = 0x0004,
|
||||
|
||||
/// <summary>Detects the range of versions including the value in VersionMin.</summary>
|
||||
VersionMinInclusive = 0x0100,
|
||||
|
||||
/// <summary>Dectects the range of versions including the value in VersionMax.</summary>
|
||||
VersionMaxInclusive = 0x0200,
|
||||
|
||||
/// <summary>Detects all languages, excluding the languages listed in the Language column.</summary>
|
||||
LanguagesExclusive = 0x0400,
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ColumnInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a single column of a table in an installer database.
|
||||
/// </summary>
|
||||
/// <remarks>Once created, a ColumnInfo object is immutable.</remarks>
|
||||
internal class ColumnInfo
|
||||
{
|
||||
private string name;
|
||||
private Type type;
|
||||
private int size;
|
||||
private bool isRequired;
|
||||
private bool isTemporary;
|
||||
private bool isLocalizable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ColumnInfo object from a column definition.
|
||||
/// </summary>
|
||||
/// <param name="name">name of the column</param>
|
||||
/// <param name="columnDefinition">column definition string</param>
|
||||
/// <seealso cref="ColumnDefinitionString"/>
|
||||
public ColumnInfo(string name, string columnDefinition)
|
||||
: this(name, typeof(String), 0, false, false, false)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
if (columnDefinition == null)
|
||||
{
|
||||
throw new ArgumentNullException("columnDefinition");
|
||||
}
|
||||
|
||||
switch (Char.ToLower(columnDefinition[0], CultureInfo.InvariantCulture))
|
||||
{
|
||||
case 'i': this.type = typeof(Int32);
|
||||
break;
|
||||
case 'j': this.type = typeof(Int32); this.isTemporary = true;
|
||||
break;
|
||||
case 'g': this.type = typeof(String); this.isTemporary = true;
|
||||
break;
|
||||
case 'l': this.type = typeof(String); this.isLocalizable = true;
|
||||
break;
|
||||
case 's': this.type = typeof(String);
|
||||
break;
|
||||
case 'v': this.type = typeof(Stream);
|
||||
break;
|
||||
default: throw new InstallerException();
|
||||
}
|
||||
|
||||
this.isRequired = Char.IsLower(columnDefinition[0]);
|
||||
this.size = Int32.Parse(
|
||||
columnDefinition.Substring(1),
|
||||
CultureInfo.InvariantCulture.NumberFormat);
|
||||
if (this.type == typeof(Int32) && this.size <= 2)
|
||||
{
|
||||
this.type = typeof(Int16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ColumnInfo object from a list of parameters.
|
||||
/// </summary>
|
||||
/// <param name="name">name of the column</param>
|
||||
/// <param name="type">type of the column; must be one of the following:
|
||||
/// Int16, Int32, String, or Stream</param>
|
||||
/// <param name="size">the maximum number of characters for String columns;
|
||||
/// ignored for other column types</param>
|
||||
/// <param name="isRequired">true if the column is required to have a non-null value</param>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public ColumnInfo(string name, Type type, int size, bool isRequired)
|
||||
: this(name, type, size, isRequired, false, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ColumnInfo object from a list of parameters.
|
||||
/// </summary>
|
||||
/// <param name="name">name of the column</param>
|
||||
/// <param name="type">type of the column; must be one of the following:
|
||||
/// Int16, Int32, String, or Stream</param>
|
||||
/// <param name="size">the maximum number of characters for String columns;
|
||||
/// ignored for other column types</param>
|
||||
/// <param name="isRequired">true if the column is required to have a non-null value</param>
|
||||
/// <param name="isTemporary">true to if the column is only in-memory and
|
||||
/// not persisted with the database</param>
|
||||
/// <param name="isLocalizable">for String columns, indicates the column
|
||||
/// is localizable; ignored for other column types</param>
|
||||
public ColumnInfo(string name, Type type, int size, bool isRequired, bool isTemporary, bool isLocalizable)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
|
||||
if (type == typeof(Int32))
|
||||
{
|
||||
size = 4;
|
||||
isLocalizable = false;
|
||||
}
|
||||
else if (type == typeof(Int16))
|
||||
{
|
||||
size = 2;
|
||||
isLocalizable = false;
|
||||
}
|
||||
else if (type == typeof(String))
|
||||
{
|
||||
}
|
||||
else if (type == typeof(Stream))
|
||||
{
|
||||
isLocalizable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("type");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
this.isRequired = isRequired;
|
||||
this.isTemporary = isTemporary;
|
||||
this.isLocalizable = isLocalizable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the column.
|
||||
/// </summary>
|
||||
/// <value>name of the column</value>
|
||||
public string Name
|
||||
{
|
||||
get { return this.name; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the column as a System.Type. This is one of the following: Int16, Int32, String, or Stream
|
||||
/// </summary>
|
||||
/// <value>type of the column</value>
|
||||
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")]
|
||||
public Type Type
|
||||
{
|
||||
get { return this.type; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the column as an integer that can be cast to a System.Data.DbType. This is one of the following: Int16, Int32, String, or Binary
|
||||
/// </summary>
|
||||
/// <value>equivalent DbType of the column as an integer</value>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public int DBType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.type == typeof(Int16)) return 10;
|
||||
else if (this.type == typeof(Int32)) return 11;
|
||||
else if (this.type == typeof(Stream)) return 1;
|
||||
else return 16;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the column.
|
||||
/// </summary>
|
||||
/// <value>The size of integer columns this is either 2 or 4. For string columns this is the maximum
|
||||
/// recommended length of the string, or 0 for unlimited length. For stream columns, 0 is returned.</value>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public int Size
|
||||
{
|
||||
get { return this.size; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the column must be non-null when inserting a record.
|
||||
/// </summary>
|
||||
/// <value>required status of the column</value>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public bool IsRequired
|
||||
{
|
||||
get { return this.isRequired; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the column is temporary. Temporary columns are not persisted
|
||||
/// when the database is saved to disk.
|
||||
/// </summary>
|
||||
/// <value>temporary status of the column</value>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public bool IsTemporary
|
||||
{
|
||||
get { return this.isTemporary; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the column is a string column that is localizable.
|
||||
/// </summary>
|
||||
/// <value>localizable status of the column</value>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public bool IsLocalizable
|
||||
{
|
||||
get { return this.isLocalizable; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an SQL fragment that can be used to create this column within a CREATE TABLE statement.
|
||||
/// </summary>
|
||||
/// <value>SQL fragment to be used for creating the column</value>
|
||||
/// <remarks><p>
|
||||
/// Examples:
|
||||
/// <list type="bullet">
|
||||
/// <item>LONG</item>
|
||||
/// <item>SHORT TEMPORARY</item>
|
||||
/// <item>CHAR(0) LOCALIZABLE</item>
|
||||
/// <item>CHAR(72) NOT NULL LOCALIZABLE</item>
|
||||
/// <item>OBJECT</item>
|
||||
/// </list>
|
||||
/// </p></remarks>
|
||||
public string SqlCreateString
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.AppendFormat("`{0}` ", this.name);
|
||||
if (this.type == typeof(Int16)) s.Append("SHORT");
|
||||
else if (this.type == typeof(Int32)) s.Append("LONG");
|
||||
else if (this.type == typeof(String)) s.AppendFormat("CHAR({0})", this.size);
|
||||
else s.Append("OBJECT");
|
||||
if (this.isRequired) s.Append(" NOT NULL");
|
||||
if (this.isTemporary) s.Append(" TEMPORARY");
|
||||
if (this.isLocalizable) s.Append(" LOCALIZABLE");
|
||||
return s.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a short string defining the type and size of the column.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The definition string consists
|
||||
/// of a single letter representing the data type followed by the width of the column (in characters
|
||||
/// when applicable, bytes otherwise). A width of zero designates an unbounded width (for example,
|
||||
/// long text fields and streams). An uppercase letter indicates that null values are allowed in
|
||||
/// the column.
|
||||
/// </value>
|
||||
/// <remarks><p>
|
||||
/// <list>
|
||||
/// <item>s? - String, variable length (?=1-255)</item>
|
||||
/// <item>s0 - String, variable length</item>
|
||||
/// <item>i2 - Short integer</item>
|
||||
/// <item>i4 - Long integer</item>
|
||||
/// <item>v0 - Binary Stream</item>
|
||||
/// <item>g? - Temporary string (?=0-255)</item>
|
||||
/// <item>j? - Temporary integer (?=0,1,2,4)</item>
|
||||
/// <item>l? - Localizable string, variable length (?=1-255)</item>
|
||||
/// <item>l0 - Localizable string, variable length</item>
|
||||
/// </list>
|
||||
/// </p></remarks>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string ColumnDefinitionString
|
||||
{
|
||||
get
|
||||
{
|
||||
char t;
|
||||
if (this.type == typeof(Int16) || this.type == typeof(Int32))
|
||||
{
|
||||
t = (this.isTemporary ? 'j' : 'i');
|
||||
}
|
||||
else if (this.type == typeof(String))
|
||||
{
|
||||
t = (this.isTemporary ? 'g' : this.isLocalizable ? 'l' : 's');
|
||||
}
|
||||
else
|
||||
{
|
||||
t = 'v';
|
||||
}
|
||||
return String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}{1}",
|
||||
(this.isRequired ? t : Char.ToUpper(t, CultureInfo.InvariantCulture)),
|
||||
this.size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the column.
|
||||
/// </summary>
|
||||
/// <returns>Name of the column.</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return this.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ComponentInfo.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for information about components within the context of an installation session.
|
||||
/// </summary>
|
||||
internal sealed class ComponentInfoCollection : ICollection<ComponentInfo>
|
||||
{
|
||||
private Session session;
|
||||
|
||||
internal ComponentInfoCollection(Session session)
|
||||
{
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about a component within the context of an installation session.
|
||||
/// </summary>
|
||||
/// <param name="component">name of the component</param>
|
||||
/// <returns>component object</returns>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public ComponentInfo this[string component]
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ComponentInfo(this.session, component);
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<ComponentInfo>.Add(ComponentInfo item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
void ICollection<ComponentInfo>.Clear()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the collection contains a component.
|
||||
/// </summary>
|
||||
/// <param name="component">name of the component</param>
|
||||
/// <returns>true if the component is in the collection, else false</returns>
|
||||
public bool Contains(string component)
|
||||
{
|
||||
return this.session.Database.CountRows(
|
||||
"Component", "`Component` = '" + component + "'") == 1;
|
||||
}
|
||||
|
||||
bool ICollection<ComponentInfo>.Contains(ComponentInfo item)
|
||||
{
|
||||
return item != null && this.Contains(item.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the features into an array.
|
||||
/// </summary>
|
||||
/// <param name="array">array that receives the features</param>
|
||||
/// <param name="arrayIndex">offset into the array</param>
|
||||
public void CopyTo(ComponentInfo[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null) {
|
||||
throw new ArgumentNullException("array");
|
||||
}
|
||||
foreach (ComponentInfo component in this)
|
||||
{
|
||||
array[arrayIndex++] = component;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of components defined for the product.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.session.Database.CountRows("Component");
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<ComponentInfo>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<ComponentInfo>.Remove(ComponentInfo item)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the components in the collection.
|
||||
/// </summary>
|
||||
/// <returns>an enumerator over all features in the collection</returns>
|
||||
public IEnumerator<ComponentInfo> GetEnumerator()
|
||||
{
|
||||
using (View compView = this.session.Database.OpenView(
|
||||
"SELECT `Component` FROM `Component`"))
|
||||
{
|
||||
compView.Execute();
|
||||
|
||||
foreach (Record compRec in compView) using (compRec)
|
||||
{
|
||||
string comp = compRec.GetString(1);
|
||||
yield return new ComponentInfo(this.session, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to information about a component within the context of an installation session.
|
||||
/// </summary>
|
||||
internal class ComponentInfo
|
||||
{
|
||||
private Session session;
|
||||
private string name;
|
||||
|
||||
internal ComponentInfo(Session session, string name)
|
||||
{
|
||||
this.session = session;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the component (primary key in the Component table).
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current install state of the designated Component.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
|
||||
/// <exception cref="ArgumentException">an unknown Component was requested</exception>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>
|
||||
/// </p></remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public InstallState CurrentState
|
||||
{
|
||||
get
|
||||
{
|
||||
int installedState, actionState;
|
||||
uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
|
||||
if (ret != 0)
|
||||
{
|
||||
if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret, this.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
}
|
||||
return (InstallState) installedState;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action state of the designated Component.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidHandleException">the Session handle is invalid</exception>
|
||||
/// <exception cref="ArgumentException">an unknown Component was requested</exception>
|
||||
/// <exception cref="InstallCanceledException">the user exited the installation</exception>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI APIs:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentstate.asp">MsiGetComponentState</a>,
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msisetcomponentstate.asp">MsiSetComponentState</a>
|
||||
/// </p></remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public InstallState RequestState
|
||||
{
|
||||
get
|
||||
{
|
||||
int installedState, actionState;
|
||||
uint ret = RemotableNativeMethods.MsiGetComponentState((int) this.session.Handle, this.name, out installedState, out actionState);
|
||||
if (ret != 0)
|
||||
{
|
||||
if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret, this.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
}
|
||||
return (InstallState) actionState;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
uint ret = RemotableNativeMethods.MsiSetComponentState((int) this.session.Handle, this.name, (int) value);
|
||||
if (ret != 0)
|
||||
{
|
||||
if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret, this.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets disk space per drive required to install a component.
|
||||
/// </summary>
|
||||
/// <param name="installState">Requested component state</param>
|
||||
/// <returns>A list of InstallCost structures, specifying the cost for each drive for the component</returns>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentcosts.asp">MsiEnumComponentCosts</a>
|
||||
/// </p></remarks>
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public IList<InstallCost> GetCost(InstallState installState)
|
||||
{
|
||||
IList<InstallCost> costs = new List<InstallCost>();
|
||||
StringBuilder driveBuf = new StringBuilder(20);
|
||||
for (uint i = 0; true; i++)
|
||||
{
|
||||
int cost, tempCost;
|
||||
uint driveBufSize = (uint) driveBuf.Capacity;
|
||||
uint ret = RemotableNativeMethods.MsiEnumComponentCosts(
|
||||
(int) this.session.Handle,
|
||||
this.name,
|
||||
i,
|
||||
(int) installState,
|
||||
driveBuf,
|
||||
ref driveBufSize,
|
||||
out cost,
|
||||
out tempCost);
|
||||
if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
|
||||
if (ret == (uint) NativeMethods.Error.MORE_DATA)
|
||||
{
|
||||
driveBuf.Capacity = (int) ++driveBufSize;
|
||||
ret = RemotableNativeMethods.MsiEnumComponentCosts(
|
||||
(int) this.session.Handle,
|
||||
this.name,
|
||||
i,
|
||||
(int) installState,
|
||||
driveBuf,
|
||||
ref driveBufSize,
|
||||
out cost,
|
||||
out tempCost);
|
||||
}
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
costs.Add(new InstallCost(driveBuf.ToString(), cost * 512L, tempCost * 512L));
|
||||
}
|
||||
return costs;
|
||||
}
|
||||
}
|
||||
}
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="ComponentInstallation.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) 1999, Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
// <summary>
|
||||
// Part of the Deployment Tools Foundation project.
|
||||
// </summary>
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.PackageManagement.Msi.Internal.Deployment.WindowsInstaller
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an instance of a registered component.
|
||||
/// </summary>
|
||||
internal class ComponentInstallation : InstallationPart
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the set of installed components for all products.
|
||||
/// </summary>
|
||||
/// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponents.asp">MsiEnumComponents</a>
|
||||
/// </p></remarks>
|
||||
public static IEnumerable<ComponentInstallation> AllComponents
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
for (uint i = 0; true; i++)
|
||||
{
|
||||
uint ret = NativeMethods.MsiEnumComponents(i, buf);
|
||||
if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
yield return new ComponentInstallation(buf.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of installed components for products in the indicated context.
|
||||
/// </summary>
|
||||
/// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/dd407947.aspx">MsiEnumComponentsEx</a>
|
||||
/// </p></remarks>
|
||||
public static IEnumerable<ComponentInstallation> Components(string szUserSid, UserContexts dwContext)
|
||||
{
|
||||
uint pcchSid = 32;
|
||||
StringBuilder szSid = new StringBuilder((int)pcchSid);
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
UserContexts installedContext;
|
||||
for (uint i = 0; true; i++)
|
||||
{
|
||||
uint ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
|
||||
if (ret == (uint) NativeMethods.Error.MORE_DATA)
|
||||
{
|
||||
szSid.EnsureCapacity((int) ++pcchSid);
|
||||
ret = NativeMethods.MsiEnumComponentsEx(szUserSid, dwContext, i, buf, out installedContext, szSid, ref pcchSid);
|
||||
}
|
||||
if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
yield return new ComponentInstallation(buf.ToString(), szSid.ToString(), installedContext);
|
||||
}
|
||||
}
|
||||
private static string GetProductCode(string component)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
uint ret = NativeMethods.MsiGetProductCode(component, buf);
|
||||
if (ret != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
private static string GetProductCode(string component, string szUserSid, UserContexts dwContext)
|
||||
{
|
||||
// TODO: We really need what would be MsiGetProductCodeEx, or at least a reasonable facimile thereof (something that restricts the search to the context defined by szUserSid & dwContext)
|
||||
return GetProductCode(component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ComponentInstallation, automatically detecting the
|
||||
/// product that the component is a part of.
|
||||
/// </summary>
|
||||
/// <param name="componentCode">component GUID</param>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetproductcode.asp">MsiGetProductCode</a>
|
||||
/// </p></remarks>
|
||||
public ComponentInstallation(string componentCode)
|
||||
: this(componentCode, ComponentInstallation.GetProductCode(componentCode))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ComponentInstallation, automatically detecting the
|
||||
/// product that the component is a part of.
|
||||
/// </summary>
|
||||
/// <param name="componentCode">component GUID</param>
|
||||
/// <param name="szUserSid">context user SID</param>
|
||||
/// <param name="dwContext">user contexts</param>
|
||||
public ComponentInstallation(string componentCode, string szUserSid, UserContexts dwContext)
|
||||
: this(componentCode, ComponentInstallation.GetProductCode(componentCode, szUserSid, dwContext), szUserSid, dwContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ComponentInstallation for a component installed by a
|
||||
/// specific product.
|
||||
/// </summary>
|
||||
/// <param name="componentCode">component GUID</param>
|
||||
/// <param name="productCode">ProductCode GUID</param>
|
||||
public ComponentInstallation(string componentCode, string productCode)
|
||||
: this(componentCode, productCode, null, UserContexts.None)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new ComponentInstallation for a component installed by a
|
||||
/// specific product.
|
||||
/// </summary>
|
||||
/// <param name="componentCode">component GUID</param>
|
||||
/// <param name="productCode">ProductCode GUID</param>
|
||||
/// <param name="szUserSid">context user SID</param>
|
||||
/// <param name="dwContext">user contexts</param>
|
||||
public ComponentInstallation(string componentCode, string productCode, string szUserSid, UserContexts dwContext)
|
||||
: base(componentCode, productCode, szUserSid, dwContext)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(componentCode))
|
||||
{
|
||||
throw new ArgumentNullException("componentCode");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component code (GUID) of the component.
|
||||
/// </summary>
|
||||
public string ComponentCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all client products of a specified component.
|
||||
/// </summary>
|
||||
/// <returns>enumeration over all client products of the component</returns>
|
||||
/// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
|
||||
/// <remarks><p>
|
||||
/// Because clients are not ordered, any new component has an arbitrary index.
|
||||
/// This means that the property may return clients in any order.
|
||||
/// </p><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumclients.asp">MsiEnumClients</a>,
|
||||
/// <a href="http://msdn.microsoft.com/library/dd407946.aspx">MsiEnumClientsEx</a>
|
||||
/// </p></remarks>
|
||||
public IEnumerable<ProductInstallation> ClientProducts
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(40);
|
||||
for (uint i = 0; true; i++)
|
||||
{
|
||||
uint chSid = 0;
|
||||
UserContexts installedContext;
|
||||
uint ret = this.Context == UserContexts.None ?
|
||||
NativeMethods.MsiEnumClients(this.ComponentCode, i, buf) :
|
||||
NativeMethods.MsiEnumClientsEx(this.ComponentCode, this.UserSid, this.Context, i, buf, out installedContext, null, ref chSid);
|
||||
if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS) break;
|
||||
else if (ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT) break;
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
yield return new ProductInstallation(buf.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installed state of a component.
|
||||
/// </summary>
|
||||
/// <returns>the installed state of the component, or InstallState.Unknown
|
||||
/// if this component is not part of a product</returns>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
|
||||
/// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>
|
||||
/// </p></remarks>
|
||||
public override InstallState State
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.ProductCode != null)
|
||||
{
|
||||
uint bufSize = 0;
|
||||
int installState = this.Context == UserContexts.None ?
|
||||
NativeMethods.MsiGetComponentPath(
|
||||
this.ProductCode, this.ComponentCode, null, ref bufSize) :
|
||||
NativeMethods.MsiGetComponentPathEx(
|
||||
this.ProductCode, this.ComponentCode, this.UserSid, this.Context, null, ref bufSize);
|
||||
return (InstallState) installState;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InstallState.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full path to an installed component. If the key path for the component is a
|
||||
/// registry key then the registry key is returned.
|
||||
/// </summary>
|
||||
/// <returns>The file or registry keypath to the component, or null if the component is not available.</returns>
|
||||
/// <exception cref="ArgumentException">An unknown product or component was specified</exception>
|
||||
/// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
|
||||
/// <remarks><p>
|
||||
/// If the component is a registry key, the registry roots are represented numerically.
|
||||
/// For example, a registry path of "HKEY_CURRENT_USER\SOFTWARE\Microsoft" would be returned
|
||||
/// as "01:\SOFTWARE\Microsoft". The registry roots returned are defined as follows:
|
||||
/// HKEY_CLASSES_ROOT=00, HKEY_CURRENT_USER=01, HKEY_LOCAL_MACHINE=02, HKEY_USERS=03,
|
||||
/// HKEY_PERFORMANCE_DATA=04
|
||||
/// </p><p>
|
||||
/// Win32 MSI APIs:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msigetcomponentpath.asp">MsiGetComponentPath</a>,
|
||||
/// <a href="http://msdn.microsoft.com/library/dd408006.aspx">MsiGetComponentPathEx</a>,
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msilocatecomponent.asp">MsiLocateComponent</a>
|
||||
/// </p></remarks>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string Path
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
uint bufSize = (uint) buf.Capacity;
|
||||
InstallState installState;
|
||||
|
||||
if (this.ProductCode != null)
|
||||
{
|
||||
installState = (this.Context == UserContexts.None) ?
|
||||
(InstallState) NativeMethods.MsiGetComponentPath(
|
||||
this.ProductCode, this.ComponentCode, buf, ref bufSize) :
|
||||
(InstallState) NativeMethods.MsiGetComponentPathEx(
|
||||
this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
|
||||
if (installState == InstallState.MoreData)
|
||||
{
|
||||
buf.Capacity = (int) ++bufSize;
|
||||
installState = (this.Context == UserContexts.None) ?
|
||||
(InstallState) NativeMethods.MsiGetComponentPath(
|
||||
this.ProductCode, this.ComponentCode, buf, ref bufSize) :
|
||||
(InstallState) NativeMethods.MsiGetComponentPathEx(
|
||||
this.ProductCode, this.ComponentCode, this.UserSid, this.Context, buf, ref bufSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
installState = (InstallState) NativeMethods.MsiLocateComponent(
|
||||
this.ComponentCode, buf, ref bufSize);
|
||||
if (installState == InstallState.MoreData)
|
||||
{
|
||||
buf.Capacity = (int) ++bufSize;
|
||||
installState = (InstallState) NativeMethods.MsiLocateComponent(
|
||||
this.ComponentCode, buf, ref bufSize);
|
||||
}
|
||||
}
|
||||
|
||||
switch (installState)
|
||||
{
|
||||
case InstallState.Local:
|
||||
case InstallState.Source:
|
||||
case InstallState.SourceAbsent:
|
||||
return buf.ToString();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of registered qualifiers for the component.
|
||||
/// </summary>
|
||||
/// <returns>Enumeration of the qulifiers for the component.</returns>
|
||||
/// <exception cref="InstallerException">The installer configuration data is corrupt</exception>
|
||||
/// <remarks><p>
|
||||
/// Because qualifiers are not ordered, any new qualifier has an arbitrary index,
|
||||
/// meaning the function can return qualifiers in any order.
|
||||
/// </p><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
|
||||
/// </p></remarks>
|
||||
public IEnumerable<ComponentInstallation.Qualifier> Qualifiers
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder qualBuf = new StringBuilder(255);
|
||||
StringBuilder dataBuf = new StringBuilder(255);
|
||||
for (uint i = 0; ; i++)
|
||||
{
|
||||
uint qualBufSize = (uint) qualBuf.Capacity;
|
||||
uint dataBufSize = (uint) dataBuf.Capacity;
|
||||
uint ret = NativeMethods.MsiEnumComponentQualifiers(
|
||||
this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
|
||||
if (ret == (uint) NativeMethods.Error.MORE_DATA)
|
||||
{
|
||||
qualBuf.Capacity = (int) ++qualBufSize;
|
||||
dataBuf.Capacity = (int) ++dataBufSize;
|
||||
ret = NativeMethods.MsiEnumComponentQualifiers(
|
||||
this.ComponentCode, i, qualBuf, ref qualBufSize, dataBuf, ref dataBufSize);
|
||||
}
|
||||
|
||||
if (ret == (uint) NativeMethods.Error.NO_MORE_ITEMS ||
|
||||
ret == (uint) NativeMethods.Error.UNKNOWN_COMPONENT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
throw InstallerException.ExceptionFromReturnCode(ret);
|
||||
}
|
||||
|
||||
yield return new ComponentInstallation.Qualifier(
|
||||
qualBuf.ToString(), dataBuf.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds data about a component qualifier.
|
||||
/// </summary>
|
||||
/// <remarks><p>
|
||||
/// Win32 MSI API:
|
||||
/// <a href="http://msdn.microsoft.com/library/en-us/msi/setup/msienumcomponentqualifiers.asp">MsiEnumComponentQualifiers</a>
|
||||
/// </p></remarks>
|
||||
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
|
||||
public struct Qualifier
|
||||
{
|
||||
private string qualifierCode;
|
||||
private string data;
|
||||
|
||||
internal Qualifier(string qualifierCode, string data)
|
||||
{
|
||||
this.qualifierCode = qualifierCode;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the qualifier code.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string QualifierCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.qualifierCode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the qualifier data.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
public string Data
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 347 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user