aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.Native
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-11-29 14:08:08 -0800
committerRob Mensching <rob@firegiant.com>2017-11-29 14:08:08 -0800
commitea3d18595a610ee07b03f07af4f03cf75b5ab420 (patch)
treeb69ac2185b05254b136051d561b189c4fda1fc5b /src/WixToolset.Core.Native
parent95f4f9b9b99e1a6f91f4687c2dd511a6d6fc2716 (diff)
downloadwix-ea3d18595a610ee07b03f07af4f03cf75b5ab420.tar.gz
wix-ea3d18595a610ee07b03f07af4f03cf75b5ab420.tar.bz2
wix-ea3d18595a610ee07b03f07af4f03cf75b5ab420.zip
Improved cabinet handling
Diffstat (limited to 'src/WixToolset.Core.Native')
-rw-r--r--src/WixToolset.Core.Native/Cabinet.cs199
-rw-r--r--src/WixToolset.Core.Native/CabinetCompressFile.cs65
-rw-r--r--src/WixToolset.Core.Native/CabinetCompressionLevel.cs25
-rw-r--r--src/WixToolset.Core.Native/CabinetFileInfo.cs67
-rw-r--r--src/WixToolset.Core.Native/WixNativeExe.cs115
-rw-r--r--src/WixToolset.Core.Native/WixToolset.Core.Native.csproj24
-rw-r--r--src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec7
7 files changed, 498 insertions, 4 deletions
diff --git a/src/WixToolset.Core.Native/Cabinet.cs b/src/WixToolset.Core.Native/Cabinet.cs
new file mode 100644
index 00000000..27b0ec74
--- /dev/null
+++ b/src/WixToolset.Core.Native/Cabinet.cs
@@ -0,0 +1,199 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8
9 /// <summary>
10 /// Wrapper class around interop with wixcab.dll to compress files into a cabinet.
11 /// </summary>
12 public sealed class Cabinet
13 {
14 private const string CompressionLevelVariable = "WIX_COMPRESSION_LEVEL";
15 private static readonly char[] TextLineSplitter = new[] { '\t' };
16
17 public Cabinet(string path)
18 {
19 this.Path = path;
20 }
21
22 public string Path { get; }
23
24 /// <summary>
25 /// Creates a cabinet.
26 /// </summary>
27 /// <param name="cabPath">Path of cabinet to create.</param>
28 /// <param name="compressionLevel">Level of compression to apply.</param>
29 /// <param name="maxFiles">Maximum number of files that will be added to cabinet.</param>
30 /// <param name="maxSize">Maximum size of cabinet.</param>
31 /// <param name="maxThresh">Maximum threshold for each cabinet.</param>
32 public void Compress(IEnumerable<CabinetCompressFile> files, CabinetCompressionLevel compressionLevel, int maxSize = 0, int maxThresh = 0)
33 {
34 var compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable);
35
36 // Override authored compression level if environment variable is present.
37 if (!String.IsNullOrEmpty(compressionLevelVariable))
38 {
39 if (!Enum.TryParse(compressionLevelVariable, true, out compressionLevel))
40 {
41 //throw new WixException(WixErrors.IllegalEnvironmentVariable(CompressionLevelVariable, compressionLevelVariable));
42 throw new ArgumentException();
43 }
44 }
45
46 var wixnative = new WixNativeExe("smartcab", this.Path, Convert.ToInt32(compressionLevel), files.Count(), maxSize, maxThresh);
47
48 foreach (var file in files)
49 {
50 wixnative.AddStdinLine(file.ToWixNativeStdinLine());
51 }
52
53 wixnative.Run();
54
55#if TOOD_ERROR_HANDLING
56 catch (COMException ce)
57 {
58 // If we get a "the file exists" error, we must have a full temp directory - so report the issue
59 if (0x80070050 == unchecked((uint)ce.ErrorCode))
60 {
61 throw new WixException(WixErrors.FullTempDirectory("WSC", Path.GetTempPath()));
62 }
63
64 throw;
65 }
66#endif
67 }
68
69 /// <summary>
70 /// Enumerates all files in a cabinet.
71 /// </summary>
72 /// <returns>>List of CabinetFileInfo</returns>
73 public List<CabinetFileInfo> Enumerate()
74 {
75 var wixnative = new WixNativeExe("enumcab", this.Path);
76 var lines = wixnative.Run();
77
78 var fileInfoList = new List<CabinetFileInfo>();
79
80 foreach (var line in lines)
81 {
82 if (String.IsNullOrEmpty(line))
83 {
84 continue;
85 }
86
87 var data = line.Split(TextLineSplitter, StringSplitOptions.None);
88
89 var size = Convert.ToInt32(data[1]);
90 var date = Convert.ToInt32(data[2]);
91 var time = Convert.ToInt32(data[3]);
92
93 fileInfoList.Add(new CabinetFileInfo(data[0], size, date, time));
94 }
95
96 return fileInfoList;
97 }
98
99 /// <summary>
100 /// Extracts all the files from a cabinet to a directory.
101 /// </summary>
102 /// <param name="outputFolder">Directory to extract files to.</param>
103 public void Extract(string outputFolder)
104 {
105 if (!outputFolder.EndsWith("\\", StringComparison.Ordinal))
106 {
107 outputFolder += "\\";
108 }
109
110 var wixnative = new WixNativeExe("extractcab", this.Path, outputFolder);
111 wixnative.Run();
112 }
113
114#if TOOD_ERROR_HANDLING
115 /// <summary>
116 /// Adds a file to the cabinet with an optional MSI file hash.
117 /// </summary>
118 /// <param name="file">The file to add.</param>
119 /// <param name="token">The token for the file.</param>
120 /// <param name="fileHash">The MSI file hash of the file.</param>
121 //private void AddFile(string file, string token, MsiInterop.MSIFILEHASHINFO fileHash)
122 //{
123 // try
124 // {
125 // NativeMethods.CreateCabAddFile(file, token, fileHash, this.handle);
126 // }
127 // catch (COMException ce)
128 // {
129 // if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL
130 // {
131 // throw new WixException(WixErrors.CreateCabAddFileFailed());
132 // }
133 // else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL
134 // {
135 // throw new WixException(WixErrors.CreateCabInsufficientDiskSpace());
136 // }
137 // else
138 // {
139 // throw;
140 // }
141 // }
142 // catch (DirectoryNotFoundException)
143 // {
144 // throw new WixFileNotFoundException(file);
145 // }
146 // catch (FileNotFoundException)
147 // {
148 // throw new WixFileNotFoundException(file);
149 // }
150 //}
151
152 /// <summary>
153 /// Complete/commit the cabinet - this must be called before Dispose so that errors will be
154 /// reported on the same thread.
155 /// </summary>
156 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
157 public void Complete(IntPtr newCabNamesCallBackAddress)
158 {
159 if (IntPtr.Zero != this.handle)
160 {
161 try
162 {
163 if (newCabNamesCallBackAddress != IntPtr.Zero && this.maxSize != 0)
164 {
165 NativeMethods.CreateCabFinish(this.handle, newCabNamesCallBackAddress);
166 }
167 else
168 {
169 NativeMethods.CreateCabFinish(this.handle, IntPtr.Zero);
170 }
171
172 GC.SuppressFinalize(this);
173 this.disposed = true;
174 }
175 catch (COMException ce)
176 {
177 //if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL
178 //{
179 // // This error seems to happen, among other situations, when cabbing more than 0xFFFF files
180 // throw new WixException(WixErrors.FinishCabFailed());
181 //}
182 //else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL
183 //{
184 // throw new WixException(WixErrors.CreateCabInsufficientDiskSpace());
185 //}
186 //else
187 //{
188 // throw;
189 //}
190 }
191 finally
192 {
193 this.handle = IntPtr.Zero;
194 }
195 }
196 }
197#endif
198 }
199}
diff --git a/src/WixToolset.Core.Native/CabinetCompressFile.cs b/src/WixToolset.Core.Native/CabinetCompressFile.cs
new file mode 100644
index 00000000..6778f4a1
--- /dev/null
+++ b/src/WixToolset.Core.Native/CabinetCompressFile.cs
@@ -0,0 +1,65 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 /// <summary>
6 /// Information to compress file into a cabinet.
7 /// </summary>
8 public sealed class CabinetCompressFile
9 {
10 /// <summary>
11 /// Cabinet compress file.
12 /// </summary>
13 /// <param name="path">Path to file to add.</param>
14 /// <param name="token">The token for the file.</param>
15 public CabinetCompressFile(string path, string token)
16 {
17 this.Path = path;
18 this.Token = token;
19 this.Hash = null;
20 }
21
22 /// <summary>
23 /// Cabinet compress file.
24 /// </summary>
25 /// <param name="path">Path to file to add.</param>
26 /// <param name="token">The token for the file.</param>
27 /// <param name="hash1">Hash 1</param>
28 /// <param name="hash2">Hash 2</param>
29 /// <param name="hash3">Hash 3</param>
30 /// <param name="hash4">Hash 4</param>
31 public CabinetCompressFile(string path, string token, int hash1, int hash2, int hash3, int hash4)
32 {
33 this.Path = path;
34 this.Token = token;
35 this.Hash = new[] { hash1, hash2, hash3, hash4 };
36 }
37
38 /// <summary>
39 /// Gets the path to the file to compress.
40 /// </summary>
41 public string Path { get; }
42
43 /// <summary>
44 /// Gets the token for the file to compress.
45 /// </summary>
46 public string Token { get; }
47
48 /// <summary>
49 /// Gets the hash of the file to compress.
50 /// </summary>
51 public int[] Hash { get; }
52
53 internal string ToWixNativeStdinLine()
54 {
55 if (this.Hash == null)
56 {
57 return $"{this.Path}\t{this.Token}";
58 }
59 else
60 {
61 return $"{this.Path}\t{this.Token}\t{this.Hash[0]}\t{this.Hash[1]}\t{this.Hash[2]}\t{this.Hash[3]}";
62 }
63 }
64 }
65}
diff --git a/src/WixToolset.Core.Native/CabinetCompressionLevel.cs b/src/WixToolset.Core.Native/CabinetCompressionLevel.cs
new file mode 100644
index 00000000..fce1ff41
--- /dev/null
+++ b/src/WixToolset.Core.Native/CabinetCompressionLevel.cs
@@ -0,0 +1,25 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 /// <summary>
6 /// Compression level to use when creating cabinet.
7 /// </summary>
8 public enum CabinetCompressionLevel
9 {
10 /// <summary>Use no compression.</summary>
11 None,
12
13 /// <summary>Use low compression.</summary>
14 Low,
15
16 /// <summary>Use medium compression.</summary>
17 Medium,
18
19 /// <summary>Use high compression.</summary>
20 High,
21
22 /// <summary>Use ms-zip compression.</summary>
23 Mszip
24 }
25} \ No newline at end of file
diff --git a/src/WixToolset.Core.Native/CabinetFileInfo.cs b/src/WixToolset.Core.Native/CabinetFileInfo.cs
new file mode 100644
index 00000000..ea229121
--- /dev/null
+++ b/src/WixToolset.Core.Native/CabinetFileInfo.cs
@@ -0,0 +1,67 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6
7 /// <summary>
8 /// Properties of a file in a cabinet.
9 /// </summary>
10 public sealed class CabinetFileInfo
11 {
12 /// <summary>
13 /// Constructs CabinetFileInfo
14 /// </summary>
15 /// <param name="fileId">File Id</param>
16 /// <param name="size">Size of file</param>
17 /// <param name="date">Last modified date</param>
18 /// <param name="time">Last modified time</param>
19 public CabinetFileInfo(string fileId, int size, int date, int time)
20 {
21 this.FileId = fileId;
22 this.Size = size;
23 this.Date = date;
24 this.Time = time;
25 }
26
27 /// <summary>
28 /// Gets the file Id of the file.
29 /// </summary>
30 /// <value>file Id</value>
31 public string FileId { get; }
32
33 /// <summary>
34 /// Gets modified date (DOS format).
35 /// </summary>
36 public int Date { get; }
37
38 /// <summary>
39 /// Gets modified time (DOS format).
40 /// </summary>
41 public int Time { get; }
42
43 /// <summary>
44 /// Gets the size of the file in bytes.
45 /// </summary>
46 public int Size { get; }
47
48 /// <summary>
49 /// Compares this file info's date and time with another datetime.
50 /// </summary>
51 /// <param name="dateTime">Date and time to compare with/</param>
52 /// <returns>
53 /// For some reason DateTime.ToLocalTime() does not match kernel32.dll FileTimeToLocalFileTime().
54 /// Since cabinets store date and time with the kernel32.dll functions, we need to convert DateTime
55 /// to local file time using the kernel32.dll functions.
56 /// </returns>
57 public bool SameAsDateTime(DateTime dateTime)
58 {
59 long filetime = dateTime.ToFileTime();
60 long localTime = 0;
61 NativeMethods.FileTimeToLocalFileTime(ref filetime, ref localTime);
62 NativeMethods.FileTimeToDosDateTime(ref localTime, out var cabDate, out var cabTime);
63
64 return this.Date == cabDate && this.Time == cabTime;
65 }
66 }
67}
diff --git a/src/WixToolset.Core.Native/WixNativeExe.cs b/src/WixToolset.Core.Native/WixNativeExe.cs
new file mode 100644
index 00000000..8626bea3
--- /dev/null
+++ b/src/WixToolset.Core.Native/WixNativeExe.cs
@@ -0,0 +1,115 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.IO;
10 using System.Reflection;
11
12 internal class WixNativeExe
13 {
14 private const int FiveMinutesInMilliseconds = 300000;
15 private static readonly string PathToWixNativeExe;
16
17 private readonly string commandLine;
18 private readonly List<string> stdinLines = new List<string>();
19
20 static WixNativeExe()
21 {
22 PathToWixNativeExe = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), "x86\\wixnative.exe");
23 }
24
25 public WixNativeExe(params object[] args)
26 {
27 this.commandLine = String.Join(" ", QuoteArgumentsAsNecesary(args));
28 }
29
30 public void AddStdinLine(string line)
31 {
32 this.stdinLines.Add(line);
33 }
34
35 public void AddStdinLines(IEnumerable<string> lines)
36 {
37 this.stdinLines.AddRange(lines);
38 }
39
40 public IEnumerable<string> Run()
41 {
42 var wixNativeInfo = new ProcessStartInfo(PathToWixNativeExe, this.commandLine)
43 {
44 RedirectStandardInput = true,
45 RedirectStandardOutput = true,
46 CreateNoWindow = true,
47 ErrorDialog = false,
48 UseShellExecute = false
49 };
50
51 var stdoutLines = new List<string>();
52
53 using (var process = Process.Start(wixNativeInfo))
54 {
55 process.OutputDataReceived += (s, a) => stdoutLines.Add(a.Data);
56 process.BeginOutputReadLine();
57
58 if (this.stdinLines.Count > 0)
59 {
60 foreach (var line in this.stdinLines)
61 {
62 process.StandardInput.WriteLine(line);
63 }
64
65 // Trailing blank line indicates stdin complete.
66 process.StandardInput.WriteLine();
67 }
68
69 if (process.WaitForExit(FiveMinutesInMilliseconds))
70 {
71 // If the process successfully exits documentation says we need to wait again
72 // without a timeout to ensure that all of the redirected output is captured.
73 //
74 process.WaitForExit();
75 }
76
77 if (process.ExitCode != 0)
78 {
79 throw new Win32Exception(process.ExitCode);
80 }
81 }
82
83 return stdoutLines;
84 }
85
86 private static IEnumerable<string> QuoteArgumentsAsNecesary(object[] args)
87 {
88 foreach (var arg in args)
89 {
90 if (arg is string str)
91 {
92 if (String.IsNullOrEmpty(str))
93 {
94 }
95 else if (str.Contains(" ") && !str.StartsWith("\""))
96 {
97 yield return $"\"{str}\"";
98 }
99 else
100 {
101 yield return str;
102 }
103 }
104 else if (arg is int i)
105 {
106 yield return i.ToString();
107 }
108 else
109 {
110 throw new ArgumentException(nameof(args));
111 }
112 }
113 }
114 }
115}
diff --git a/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj b/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj
index 3e66d84e..aa87186b 100644
--- a/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj
+++ b/src/WixToolset.Core.Native/WixToolset.Core.Native.csproj
@@ -3,6 +3,7 @@
3 <PropertyGroup> 3 <PropertyGroup>
4 <TargetFramework>netstandard2.0</TargetFramework> 4 <TargetFramework>netstandard2.0</TargetFramework>
5 <NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile> 5 <NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile>
6 <Description>Core Native</Description>
6 <!-- <BeforePack>SetNuspecProperties</BeforePack> --> 7 <!-- <BeforePack>SetNuspecProperties</BeforePack> -->
7 </PropertyGroup> 8 </PropertyGroup>
8 9
@@ -10,9 +11,26 @@
10 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> 11 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
11 </ItemGroup> 12 </ItemGroup>
12 13
13 <!-- <ItemGroup> 14 <Target Name="BuildWixNative"
14 <ProjectReference Include="..\winterop\winterop.vcxproj" ExcludeAssets="All" /> 15 BeforeTargets="GetCopyToOutputDirectoryItems">
15 </ItemGroup> --> 16 <MSBuild Projects="..\wixnative\wixnative.vcxproj" Properties="Platform=Win32" />
17 <MSBuild Projects="..\wixnative\wixnative.vcxproj" Properties="Platform=x64" />
18
19 <ItemGroup>
20 <NativeProjectOutput Include="$(OutputPath)..\Win32\*.exe;$(OutputPath)..\Win32\*.pdb">
21 <TargetRelativeFolder>x86\</TargetRelativeFolder>
22 </NativeProjectOutput>
23 <NativeProjectOutput Include="$(OutputPath)..\x64\*.exe;$(OutputPath)..\x64\*.pdb">
24 <TargetRelativeFolder>x64\</TargetRelativeFolder>
25 </NativeProjectOutput>
26
27 <AllItemsFullPathWithTargetPath Include="@(NativeProjectOutput->'%(FullPath)')">
28 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
29 <TargetPath>%(TargetRelativeFolder)%(Filename)%(Extension)</TargetPath>
30 </AllItemsFullPathWithTargetPath>
31 </ItemGroup>
32 </Target>
33
16 <Target Name="SetNuspecProperties" 34 <Target Name="SetNuspecProperties"
17 AfterTargets="Build"> 35 AfterTargets="Build">
18 <PropertyGroup> 36 <PropertyGroup>
diff --git a/src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec b/src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
index 68d154c8..6a96167e 100644
--- a/src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
+++ b/src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
@@ -10,15 +10,20 @@
10 <requireLicenseAcceptance>false</requireLicenseAcceptance> 10 <requireLicenseAcceptance>false</requireLicenseAcceptance>
11 <description>$description$</description> 11 <description>$description$</description>
12 <copyright>$copyright$</copyright> 12 <copyright>$copyright$</copyright>
13 13<!--
14 <dependencies> 14 <dependencies>
15 <dependency id="runtime.win-x86.WixToolset.Core.Native" version="$version$" /> 15 <dependency id="runtime.win-x86.WixToolset.Core.Native" version="$version$" />
16 <dependency id="runtime.win-x64.WixToolset.Core.Native" version="$version$" /> 16 <dependency id="runtime.win-x64.WixToolset.Core.Native" version="$version$" />
17 </dependencies> 17 </dependencies>
18 -->
18 </metadata> 19 </metadata>
19 20
20 <files> 21 <files>
21 <file src="$id$.dll" target="lib\netstandard2.0" /> 22 <file src="$id$.dll" target="lib\netstandard2.0" />
22 <file src="$id$.pdb" target="lib\netstandard2.0" /> 23 <file src="$id$.pdb" target="lib\netstandard2.0" />
24 <file src="..\Win32\wixnative.exe" target="lib\netstandard2.0\x86" />
25 <file src="..\Win32\wixnative.pdb" target="lib\netstandard2.0\x86" />
26 <file src="..\x64\wixnative.exe" target="lib\netstandard2.0\x64" />
27 <file src="..\x64\wixnative.pdb" target="lib\netstandard2.0\x64" />
23 </files> 28 </files>
24</package> 29</package>