Improved cabinet handling
Rob Mensching committed
Nov 29, 2017 at 14:08 UTC
ea3d18595a610ee07b03f07af4f03cf75b5ab420
25 files changed
+1276
-8
WixToolset.Core.Native.sln
+37
-1
@@ -1,7 +1,7 @@
1
2
Microsoft Visual Studio Solution File, Format Version 12.00
3
# Visual Studio 15
4
-VisualStudioVersion = 15.0.26730.12
4
+VisualStudioVersion = 15.0.27004.2009
5
MinimumVisualStudioVersion = 15.0.26124.0
6
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winterop", "src\winterop\winterop.vcxproj", "{26D45E58-E703-431D-B67E-493C72C9DA0B}"
7
EndProject
@@ -10,28 +10,64 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Native", "s
10
{26D45E58-E703-431D-B67E-493C72C9DA0B} = {26D45E58-E703-431D-B67E-493C72C9DA0B}
11
EndProjectSection
12
EndProject
13
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wixnative", "src\wixnative\wixnative.vcxproj", "{8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}"
14
+EndProject
15
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Native", "src\test\WixToolsetTest.Core.Native\WixToolsetTest.Core.Native.csproj", "{54F5329A-C113-471A-8EE1-83021E8A4853}"
16
+EndProject
17
Global
18
GlobalSection(SolutionConfigurationPlatforms) = preSolution
19
Debug|Any CPU = Debug|Any CPU
20
+ Debug|x64 = Debug|x64
21
Debug|x86 = Debug|x86
22
Release|Any CPU = Release|Any CPU
23
+ Release|x64 = Release|x64
24
Release|x86 = Release|x86
25
EndGlobalSection
26
GlobalSection(ProjectConfigurationPlatforms) = postSolution
27
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Debug|Any CPU.ActiveCfg = Debug|Win32
28
+ {26D45E58-E703-431D-B67E-493C72C9DA0B}.Debug|x64.ActiveCfg = Debug|x64
29
+ {26D45E58-E703-431D-B67E-493C72C9DA0B}.Debug|x64.Build.0 = Debug|x64
30
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Debug|x86.ActiveCfg = Debug|Win32
31
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Debug|x86.Build.0 = Debug|Win32
32
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Release|Any CPU.ActiveCfg = Release|Win32
33
+ {26D45E58-E703-431D-B67E-493C72C9DA0B}.Release|x64.ActiveCfg = Release|x64
34
+ {26D45E58-E703-431D-B67E-493C72C9DA0B}.Release|x64.Build.0 = Release|x64
35
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Release|x86.ActiveCfg = Release|Win32
36
{26D45E58-E703-431D-B67E-493C72C9DA0B}.Release|x86.Build.0 = Release|Win32
37
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|Any CPU.Build.0 = Debug|Any CPU
39
+ {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x64.ActiveCfg = Debug|Any CPU
40
+ {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x64.Build.0 = Debug|Any CPU
41
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x86.ActiveCfg = Debug|Any CPU
42
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Debug|x86.Build.0 = Debug|Any CPU
43
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|Any CPU.ActiveCfg = Release|Any CPU
44
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|Any CPU.Build.0 = Release|Any CPU
45
+ {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x64.ActiveCfg = Release|Any CPU
46
+ {C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x64.Build.0 = Release|Any CPU
47
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x86.ActiveCfg = Release|Any CPU
48
{C1F36B7C-6A5B-44CB-BD05-3C9CDEC2DD63}.Release|x86.Build.0 = Release|Any CPU
49
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|Any CPU.ActiveCfg = Debug|Win32
50
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x64.ActiveCfg = Debug|x64
51
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x64.Build.0 = Debug|x64
52
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x86.ActiveCfg = Debug|Win32
53
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Debug|x86.Build.0 = Debug|Win32
54
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|Any CPU.ActiveCfg = Release|Win32
55
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x64.ActiveCfg = Release|x64
56
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x64.Build.0 = Release|x64
57
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x86.ActiveCfg = Release|Win32
58
+ {8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}.Release|x86.Build.0 = Release|Win32
59
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|Any CPU.Build.0 = Debug|Any CPU
61
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x64.ActiveCfg = Debug|Any CPU
62
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x64.Build.0 = Debug|Any CPU
63
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x86.ActiveCfg = Debug|Any CPU
64
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Debug|x86.Build.0 = Debug|Any CPU
65
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|Any CPU.ActiveCfg = Release|Any CPU
66
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|Any CPU.Build.0 = Release|Any CPU
67
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x64.ActiveCfg = Release|Any CPU
68
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x64.Build.0 = Release|Any CPU
69
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x86.ActiveCfg = Release|Any CPU
70
+ {54F5329A-C113-471A-8EE1-83021E8A4853}.Release|x86.Build.0 = Release|Any CPU
71
EndGlobalSection
72
GlobalSection(SolutionProperties) = preSolution
73
HideSolutionNode = FALSE
appveyor.cmd
+2
-3
@@ -3,10 +3,9 @@
3
4
nuget restore
5
6
-msbuild -p:Configuration=Release;Platform=Win32 -t:PackNativeNuget src\winterop\winterop.vcxproj
7
-msbuild -p:Configuration=Release;Platform=x64 -t:PackNativeNuget src\winterop\winterop.vcxproj
6
+msbuild -p:Configuration=Release .\src\test\WixToolsetTest.Core.Native\WixToolsetTest.Core.Native.csproj
7
9
-dotnet pack -c Release .\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj
8
+msbuild -t:Pack -p:Configuration=Release .\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj
9
10
@popd
11
@endlocal
\ No newline at end of file
src/WixToolset.Core.Native/Cabinet.cs
new
+199
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core.Native/CabinetCompressFile.cs
new
+65
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core.Native/CabinetCompressionLevel.cs
new
+25
@@ -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
+
3
+namespace 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
src/WixToolset.Core.Native/CabinetFileInfo.cs
new
+67
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core.Native/WixNativeExe.cs
new
+115
@@ -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
+
3
+namespace 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
+}
src/WixToolset.Core.Native/WixToolset.Core.Native.csproj
+21
-3
@@ -3,6 +3,7 @@
3
<PropertyGroup>
4
<TargetFramework>netstandard2.0</TargetFramework>
5
<NuspecFile>$(MSBuildThisFileName).nuspec</NuspecFile>
6
+ <Description>Core Native</Description>
7
<!-- <BeforePack>SetNuspecProperties</BeforePack> -->
8
</PropertyGroup>
9
@@ -10,9 +11,26 @@
11
<PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
12
</ItemGroup>
13
13
- <!-- <ItemGroup>
14
- <ProjectReference Include="..\winterop\winterop.vcxproj" ExcludeAssets="All" />
15
- </ItemGroup> -->
14
+ <Target Name="BuildWixNative"
15
+ BeforeTargets="GetCopyToOutputDirectoryItems">
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
+
34
<Target Name="SetNuspecProperties"
35
AfterTargets="Build">
36
<PropertyGroup>
src/WixToolset.Core.Native/WixToolset.Core.Native.nuspec
+6
-1
@@ -10,15 +10,20 @@
10
<requireLicenseAcceptance>false</requireLicenseAcceptance>
11
<description>$description$</description>
12
<copyright>$copyright$</copyright>
13
-
13
+<!--
14
<dependencies>
15
<dependency id="runtime.win-x86.WixToolset.Core.Native" version="$version$" />
16
<dependency id="runtime.win-x64.WixToolset.Core.Native" version="$version$" />
17
</dependencies>
18
+ -->
19
</metadata>
20
21
<files>
22
<file src="$id$.dll" 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" />
28
</files>
29
</package>
src/test/WixToolsetTest.Core.Native/CabinetFixture.cs
new
+115
@@ -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
+
3
+namespace WixToolsetTest.CoreNative
4
+{
5
+ using System;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using WixToolset.Core.Native;
9
+ using WixToolsetTest.CoreNative.Utility;
10
+ using Xunit;
11
+
12
+ public class CabinetFixture
13
+ {
14
+ [Fact]
15
+ public void CanCreateSingleFileCabinet()
16
+ {
17
+ using (var fs = new DisposableFileSystem())
18
+ {
19
+ var intermediateFolder = fs.GetFolder(true);
20
+ var cabPath = Path.Combine(intermediateFolder, "testout.cab");
21
+
22
+ var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test.txt") };
23
+
24
+ var cabinet = new Cabinet(cabPath);
25
+ cabinet.Compress(files, CabinetCompressionLevel.Low);
26
+
27
+ Assert.True(File.Exists(cabPath));
28
+ }
29
+ }
30
+
31
+ [Fact]
32
+ public void CanEnumerateSingleFileCabinet()
33
+ {
34
+ var cabinetPath = TestData.Get(@"TestData\test.cab");
35
+
36
+ var cabinet = new Cabinet(cabinetPath);
37
+ var files = cabinet.Enumerate();
38
+
39
+ var file = files.Single();
40
+ Assert.Equal("test.txt", file.FileId);
41
+ Assert.Equal(17, file.Size);
42
+
43
+ Assert.Equal(19259, file.Date);
44
+ Assert.Equal(47731, file.Time);
45
+ Assert.True(file.SameAsDateTime(new DateTime(2017, 9, 28, 0, 19, 38)));
46
+ }
47
+
48
+ [Fact]
49
+ public void CanExtractSingleFileCabinet()
50
+ {
51
+ var cabinetPath = TestData.Get(@"TestData\test.cab");
52
+
53
+ using (var fs = new DisposableFileSystem())
54
+ {
55
+ var extractFolder = fs.GetFolder(true);
56
+
57
+ var cabinet = new Cabinet(cabinetPath);
58
+ cabinet.Extract(extractFolder);
59
+
60
+ var files = Directory.EnumerateFiles(extractFolder);
61
+
62
+ var file = new FileInfo(files.Single());
63
+ CabInterop.DateTimeToCabDateAndTime(file.CreationTime, out var date, out var time);
64
+
65
+ Assert.Equal("test.txt", file.Name);
66
+ Assert.Equal(17, file.Length);
67
+ Assert.Equal(19259, date);
68
+ Assert.Equal(47731, time);
69
+ }
70
+ }
71
+
72
+ [Fact]
73
+ public void IntegrationTest()
74
+ {
75
+ using (var fs = new DisposableFileSystem())
76
+ {
77
+ var intermediateFolder = fs.GetFolder(true);
78
+ var cabinetPath = Path.Combine(intermediateFolder, "testout.cab");
79
+ var extractFolder = fs.GetFolder(true);
80
+
81
+ // Compress.
82
+ {
83
+ var files = new[] { new CabinetCompressFile(TestData.Get(@"TestData\test.txt"), "test.txt") };
84
+
85
+ var cabinet = new Cabinet(cabinetPath);
86
+ cabinet.Compress(files, CabinetCompressionLevel.Low);
87
+ }
88
+
89
+ // Extract.
90
+ {
91
+ var cabinet = new Cabinet(cabinetPath);
92
+ cabinet.Extract(extractFolder);
93
+ }
94
+
95
+ // Enumerate to compare cabinet to extracted files.
96
+ {
97
+ var cabinet = new Cabinet(cabinetPath);
98
+ var enumerated = cabinet.Enumerate().OrderBy(f => f.FileId).ToArray();
99
+
100
+ var files = Directory.EnumerateFiles(extractFolder).OrderBy(f => f).ToArray();
101
+
102
+ for (var i =0; i < enumerated.Length; ++i)
103
+ {
104
+ var cabFileInfo = enumerated[i];
105
+ var fileInfo = new FileInfo(files[i]);
106
+
107
+ Assert.Equal(cabFileInfo.FileId, fileInfo.Name);
108
+ Assert.Equal(cabFileInfo.Size, fileInfo.Length);
109
+ Assert.True(cabFileInfo.SameAsDateTime(fileInfo.CreationTime));
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+}
src/test/WixToolsetTest.Core.Native/TestData/test.cab
Binary files /dev/null and b/src/test/WixToolsetTest.Core.Native/TestData/test.cab differ
src/test/WixToolsetTest.Core.Native/TestData/test.txt
new
+1
@@ -0,0 +1 @@
1
+This is test.txt.
\ No newline at end of file
src/test/WixToolsetTest.Core.Native/Utility/DisposableFileSystem.cs
new
+86
@@ -0,0 +1,86 @@
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
+
3
+namespace WixToolsetTest.CoreNative.Utility
4
+{
5
+ using System;
6
+ using System.Collections.Generic;
7
+ using System.IO;
8
+
9
+ public class DisposableFileSystem : IDisposable
10
+ {
11
+ protected bool Disposed { get; private set; }
12
+
13
+ private List<string> CleanupPaths { get; } = new List<string>();
14
+
15
+ public string GetFile(bool create = false)
16
+ {
17
+ var path = Path.GetTempFileName();
18
+
19
+ if (!create)
20
+ {
21
+ File.Delete(path);
22
+ }
23
+
24
+ this.CleanupPaths.Add(path);
25
+
26
+ return path;
27
+ }
28
+
29
+ public string GetFolder(bool create = false)
30
+ {
31
+ var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
32
+
33
+ if (create)
34
+ {
35
+ Directory.CreateDirectory(path);
36
+ }
37
+
38
+ this.CleanupPaths.Add(path);
39
+
40
+ return path;
41
+ }
42
+
43
+
44
+ #region // IDisposable
45
+
46
+ public void Dispose()
47
+ {
48
+ this.Dispose(true);
49
+ GC.SuppressFinalize(this);
50
+ }
51
+
52
+ protected virtual void Dispose(bool disposing)
53
+ {
54
+ if (this.Disposed)
55
+ {
56
+ return;
57
+ }
58
+
59
+ if (disposing)
60
+ {
61
+ foreach (var path in this.CleanupPaths)
62
+ {
63
+ try
64
+ {
65
+ if (File.Exists(path))
66
+ {
67
+ File.Delete(path);
68
+ }
69
+ else if (Directory.Exists(path))
70
+ {
71
+ Directory.Delete(path, true);
72
+ }
73
+ }
74
+ catch
75
+ {
76
+ // Best effort delete, so ignore any failures.
77
+ }
78
+ }
79
+ }
80
+
81
+ this.Disposed = true;
82
+ }
83
+
84
+ #endregion
85
+ }
86
+}
src/test/WixToolsetTest.Core.Native/Utility/Pushd.cs
new
+46
@@ -0,0 +1,46 @@
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
+
3
+namespace WixToolsetTest.CoreNative.Utility
4
+{
5
+ using System;
6
+ using System.IO;
7
+
8
+ public class Pushd : IDisposable
9
+ {
10
+ protected bool Disposed { get; private set; }
11
+
12
+ public Pushd(string path)
13
+ {
14
+ this.PreviousDirectory = Directory.GetCurrentDirectory();
15
+
16
+ Directory.SetCurrentDirectory(path);
17
+ }
18
+
19
+ public string PreviousDirectory { get; }
20
+
21
+ #region // IDisposable
22
+
23
+ public void Dispose()
24
+ {
25
+ this.Dispose(true);
26
+ GC.SuppressFinalize(this);
27
+ }
28
+
29
+ protected virtual void Dispose(bool disposing)
30
+ {
31
+ if (this.Disposed)
32
+ {
33
+ return;
34
+ }
35
+
36
+ if (disposing)
37
+ {
38
+ Directory.SetCurrentDirectory(this.PreviousDirectory);
39
+ }
40
+
41
+ this.Disposed = true;
42
+ }
43
+
44
+ #endregion
45
+ }
46
+}
src/test/WixToolsetTest.Core.Native/Utility/TestData.cs
new
+17
@@ -0,0 +1,17 @@
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
+
3
+namespace WixToolsetTest.CoreNative.Utility
4
+{
5
+ using System;
6
+ using System.IO;
7
+
8
+ public class TestData
9
+ {
10
+ public static string LocalPath => Path.GetDirectoryName(new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath);
11
+
12
+ public static string Get(params string[] paths)
13
+ {
14
+ return Path.Combine(LocalPath, Path.Combine(paths));
15
+ }
16
+ }
17
+}
src/test/WixToolsetTest.Core.Native/WixToolsetTest.Core.Native.csproj
new
+24
@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- 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. -->
3
+
4
+<Project Sdk="Microsoft.NET.Sdk">
5
+ <PropertyGroup>
6
+ <TargetFramework>netcoreapp2.0</TargetFramework>
7
+ <IsPackable>false</IsPackable>
8
+ </PropertyGroup>
9
+
10
+ <ItemGroup>
11
+ <Content Include="TestData\test.cab" CopyToOutputDirectory="PreserveNewest" />
12
+ <Content Include="TestData\test.txt" CopyToOutputDirectory="PreserveNewest" />
13
+ </ItemGroup>
14
+
15
+ <ItemGroup>
16
+ <ProjectReference Include="..\..\WixToolset.Core.Native\WixToolset.Core.Native.csproj" />
17
+ </ItemGroup>
18
+
19
+ <ItemGroup>
20
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
21
+ <PackageReference Include="xunit" Version="2.2.0" />
22
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
23
+ </ItemGroup>
24
+</Project>
src/wixnative/enumcab.cpp
new
+47
@@ -0,0 +1,47 @@
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
+
3
+#include "precomp.h"
4
+
5
+static INT_PTR __stdcall EnumCallback(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin);
6
+
7
+
8
+HRESULT EnumCabCommand(
9
+ __in int argc,
10
+ __in LPWSTR argv[]
11
+)
12
+{
13
+ HRESULT hr = E_INVALIDARG;
14
+ LPCWSTR wzCabPath = NULL;
15
+
16
+ if (argc < 1)
17
+ {
18
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: cabPath outputFolder");
19
+ }
20
+
21
+ wzCabPath = argv[0];
22
+
23
+ hr = CabInitialize(FALSE);
24
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
25
+
26
+ hr = CabEnumerate(wzCabPath, L"*", EnumCallback, 0);
27
+ ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
28
+
29
+LExit:
30
+ CabUninitialize();
31
+
32
+ return hr;
33
+}
34
+
35
+
36
+static INT_PTR __stdcall EnumCallback(
37
+ __in FDINOTIFICATIONTYPE fdint,
38
+ __in PFDINOTIFICATION pfdin
39
+)
40
+{
41
+ if (fdint == fdintCOPY_FILE)
42
+ {
43
+ ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%s\t%d\t%u\t%u", pfdin->psz1, pfdin->cb, pfdin->date, pfdin->time);
44
+ }
45
+
46
+ return 0;
47
+}
src/wixnative/extractcab.cpp
new
+50
@@ -0,0 +1,50 @@
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
+
3
+#include "precomp.h"
4
+
5
+static HRESULT ProgressCallback(BOOL fBeginFile, LPCWSTR wzFileId, LPVOID pvContext);
6
+
7
+
8
+HRESULT ExtractCabCommand(
9
+ __in int argc,
10
+ __in LPWSTR argv[]
11
+)
12
+{
13
+ HRESULT hr = E_INVALIDARG;
14
+ LPCWSTR wzCabPath = NULL;
15
+ LPCWSTR wzOutputFolder = NULL;
16
+
17
+ if (argc < 2)
18
+ {
19
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: cabPath outputFolder");
20
+ }
21
+
22
+ wzCabPath = argv[0];
23
+ wzOutputFolder = argv[1];
24
+
25
+ hr = CabInitialize(FALSE);
26
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
27
+
28
+ hr = CabExtract(wzCabPath, L"*", wzOutputFolder, ProgressCallback, NULL, 0);
29
+ ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
30
+
31
+LExit:
32
+ CabUninitialize();
33
+
34
+ return hr;
35
+}
36
+
37
+
38
+static HRESULT ProgressCallback(
39
+ __in BOOL fBeginFile,
40
+ __in LPCWSTR wzFileId,
41
+ __in LPVOID /*pvContext*/
42
+)
43
+{
44
+ if (fBeginFile)
45
+ {
46
+ ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls", wzFileId);
47
+ }
48
+
49
+ return S_OK;
50
+}
src/wixnative/packages.config
new
+5
@@ -0,0 +1,5 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<packages>
3
+ <package id="Nerdbank.GitVersioning" version="2.0.41" targetFramework="native" developmentDependency="true" />
4
+ <package id="WixToolset.DUtil" version="4.0.3" targetFramework="native" />
5
+</packages>
\ No newline at end of file
src/wixnative/precomp.cpp
new
+3
@@ -0,0 +1,3 @@
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
+
3
+#include "precomp.h"
src/wixnative/precomp.h
new
+19
@@ -0,0 +1,19 @@
1
+#pragma once
2
+// 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.
3
+
4
+#include <windows.h>
5
+#include <aclapi.h>
6
+#include <mergemod.h>
7
+
8
+#include "dutil.h"
9
+#include "conutil.h"
10
+#include "memutil.h"
11
+#include "pathutil.h"
12
+#include "strutil.h"
13
+#include "cabcutil.h"
14
+#include "cabutil.h"
15
+
16
+HRESULT SmartCabCommand(int argc, LPWSTR argv[]);
17
+HRESULT ResetAclsCommand(int argc, LPWSTR argv[]);
18
+HRESULT EnumCabCommand(int argc, LPWSTR argv[]);
19
+HRESULT ExtractCabCommand(int argc, LPWSTR argv[]);
src/wixnative/resetacls.cpp
new
+51
@@ -0,0 +1,51 @@
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
+
3
+#include "precomp.h"
4
+
5
+HRESULT ResetAclsCommand(int argc, LPWSTR argv[])
6
+{
7
+ Unused(argc);
8
+ Unused(argv);
9
+
10
+ HRESULT hr = S_OK;
11
+ ACL* pacl = NULL;
12
+ DWORD cbAcl = sizeof(ACL);
13
+ LPWSTR sczFilePath = NULL;
14
+
15
+ // create an empty (not NULL!) ACL to use on all the files
16
+ pacl = static_cast<ACL*>(MemAlloc(cbAcl, FALSE));
17
+ ConsoleExitOnNull(pacl, hr, E_OUTOFMEMORY, CONSOLE_COLOR_RED, "failed to allocate ACL");
18
+
19
+#pragma prefast(push)
20
+#pragma prefast(disable:25029)
21
+ if (!::InitializeAcl(pacl, cbAcl, ACL_REVISION))
22
+#pragma prefast(op)
23
+ {
24
+ ConsoleExitOnLastError(hr, CONSOLE_COLOR_RED, "failed to initialize ACL");
25
+ }
26
+
27
+ // Reset the existing security permissions on each provided file.
28
+ for (;;)
29
+ {
30
+ hr = ConsoleReadW(&sczFilePath);
31
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to read file path from stdin");
32
+
33
+ if (!*sczFilePath)
34
+ {
35
+ break;
36
+ }
37
+
38
+ hr = ::SetNamedSecurityInfoW(sczFilePath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pacl, NULL);
39
+ if (ERROR_FILE_NOT_FOUND != hr && ERROR_PATH_NOT_FOUND != hr)
40
+ {
41
+ ConsoleExitOnFailure(hr = HRESULT_FROM_WIN32(hr), CONSOLE_COLOR_RED, "failed to set security descriptor for file: %ls", sczFilePath);
42
+ }
43
+ }
44
+
45
+ AssertSz(::IsValidAcl(pacl), "ResetAcls() - created invalid ACL");
46
+
47
+LExit:
48
+ ReleaseStr(sczFilePath);
49
+ ReleaseMem(pacl);
50
+ return hr;
51
+}
src/wixnative/smartcab.cpp
new
+157
@@ -0,0 +1,157 @@
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
+
3
+#include "precomp.h"
4
+
5
+static HRESULT CompressFiles(HANDLE hCab);
6
+static void __stdcall CabNamesCallback(LPWSTR wzFirstCabName, LPWSTR wzNewCabName, LPWSTR wzFileToken);
7
+
8
+
9
+HRESULT SmartCabCommand(
10
+ __in int argc,
11
+ __in LPWSTR argv[]
12
+)
13
+{
14
+ HRESULT hr = E_INVALIDARG;
15
+ LPCWSTR wzCabPath = NULL;
16
+ LPCWSTR wzCabName = NULL;
17
+ LPWSTR sczCabDir = NULL;
18
+ UINT uiFileCount = 0;
19
+ UINT uiMaxSize = 0;
20
+ UINT uiMaxThresh = 0;
21
+ COMPRESSION_TYPE ct = COMPRESSION_TYPE_NONE;
22
+ HANDLE hCab = NULL;
23
+
24
+ if (argc < 1)
25
+ {
26
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Must specify: outCabPath [compressionType] [fileCount] [maxSizePerCabInMB [maxThreshold]]");
27
+ }
28
+ else
29
+ {
30
+ wzCabPath = argv[0];
31
+ wzCabName = PathFile(wzCabPath);
32
+
33
+ hr = PathGetDirectory(wzCabPath, &sczCabDir);
34
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse directory from path: %ls", wzCabPath);
35
+
36
+ if (argc > 1)
37
+ {
38
+ UINT uiCompressionType;
39
+ hr = StrStringToUInt32(argv[1], 0, &uiCompressionType);
40
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse compression type as number: %ls", argv[1]);
41
+
42
+ ct = (uiCompressionType > 4) ? COMPRESSION_TYPE_HIGH : static_cast<COMPRESSION_TYPE>(uiCompressionType);
43
+ }
44
+
45
+ if (argc > 2)
46
+ {
47
+ hr = StrStringToUInt32(argv[2], 0, &uiFileCount);
48
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse file count as number: %ls", argv[2]);
49
+ }
50
+
51
+ if (argc > 3)
52
+ {
53
+ hr = StrStringToUInt32(argv[3], 0, &uiMaxSize);
54
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse max size as number: %ls", argv[3]);
55
+ }
56
+
57
+ if (argc > 4)
58
+ {
59
+ hr = StrStringToUInt32(argv[4], 0, &uiMaxThresh);
60
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Could not parse max threshold as number: %ls", argv[4]);
61
+ }
62
+ }
63
+
64
+ hr = CabCBegin(wzCabName, sczCabDir, uiFileCount, uiMaxSize, uiMaxThresh, ct, &hCab);
65
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to initialize cabinet: %ls", wzCabPath);
66
+
67
+ hr = CompressFiles(hCab);
68
+ ExitOnFailure(hr, "failed to compress files into cabinet: %ls", wzCabPath);
69
+
70
+ hr = CabCFinish(hCab, CabNamesCallback);
71
+ hCab = NULL; // once finish is called, the handle is invalid.
72
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to compress cabinet: %ls", wzCabPath);
73
+
74
+
75
+LExit:
76
+ if (hCab)
77
+ {
78
+ CabCCancel(hCab);
79
+ }
80
+ ReleaseStr(sczCabDir);
81
+
82
+ return hr;
83
+}
84
+
85
+
86
+static HRESULT CompressFiles(
87
+ __in HANDLE hCab
88
+)
89
+{
90
+ HRESULT hr = S_OK;
91
+ LPWSTR sczLine = NULL;
92
+ LPWSTR* rgsczSplit = NULL;
93
+ UINT cSplit = 0;
94
+ MSIFILEHASHINFO hashInfo = { sizeof(MSIFILEHASHINFO) };
95
+
96
+ for (;;)
97
+ {
98
+ hr = ConsoleReadW(&sczLine);
99
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to read smartcab line from stdin");
100
+
101
+ if (!*sczLine)
102
+ {
103
+ break;
104
+ }
105
+
106
+ hr = StrSplitAllocArray(&rgsczSplit, &cSplit, sczLine, L"\t");
107
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to split smartcab line from stdin: %ls", sczLine);
108
+
109
+ if (cSplit != 2 && cSplit != 6)
110
+ {
111
+ hr = E_INVALIDARG;
112
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to split smartcab line into hash x 4, token, source file: %ls", sczLine);
113
+ }
114
+
115
+ LPCWSTR wzFilePath = rgsczSplit[0];
116
+ LPCWSTR wzToken = rgsczSplit[1];
117
+ PMSIFILEHASHINFO pHashInfo = NULL;
118
+
119
+ if (cSplit == 6)
120
+ {
121
+ for (int i = 0; i < 4; ++i)
122
+ {
123
+ LPCWSTR wzHash = rgsczSplit[i + 2];
124
+
125
+ hr = StrStringToInt32(wzHash, 0, reinterpret_cast<INT*>(hashInfo.dwData + i));
126
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to parse hash: %ls for file: %ls", wzHash, wzFilePath);
127
+ }
128
+
129
+ pHashInfo = &hashInfo;
130
+ }
131
+
132
+ hr = CabCAddFile(wzFilePath, wzToken, pHashInfo, hCab);
133
+ ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "failed to add file: %ls", wzFilePath);
134
+
135
+ ReleaseNullStrArray(rgsczSplit, cSplit);
136
+ }
137
+
138
+LExit:
139
+ ReleaseNullStrArray(rgsczSplit, cSplit);
140
+ ReleaseStr(sczLine);
141
+
142
+ return hr;
143
+}
144
+
145
+
146
+// Callback from PFNFCIGETNEXTCABINET CabCGetNextCabinet method
147
+// First argument is the name of splitting cabinet without extension e.g. "cab1"
148
+// Second argument is name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"
149
+// Third argument is the file token of the first file present in the splitting cabinet
150
+static void __stdcall CabNamesCallback(
151
+ __in LPWSTR wzFirstCabName,
152
+ __in LPWSTR wzNewCabName,
153
+ __in LPWSTR wzFileToken
154
+)
155
+{
156
+ ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls", wzFirstCabName, wzNewCabName, wzFileToken);
157
+}
src/wixnative/wixnative.cpp
new
+38
@@ -0,0 +1,38 @@
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
+
3
+#include "precomp.h"
4
+
5
+int __cdecl wmain(int argc, LPWSTR argv[])
6
+{
7
+ HRESULT hr = E_INVALIDARG;
8
+
9
+ ConsoleInitialize();
10
+
11
+ if (argc < 2)
12
+ {
13
+ ConsoleWriteError(hr, CONSOLE_COLOR_RED, "Must specify a command");
14
+ }
15
+ else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"smartcab", -1))
16
+ {
17
+ hr = SmartCabCommand(argc - 2, argv + 2);
18
+ }
19
+ else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"extractcab", -1))
20
+ {
21
+ hr = ExtractCabCommand(argc - 2, argv + 2);
22
+ }
23
+ else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"enumcab", -1))
24
+ {
25
+ hr = EnumCabCommand(argc - 2, argv + 2);
26
+ }
27
+ else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1))
28
+ {
29
+ hr = ResetAclsCommand(argc - 2, argv + 2);
30
+ }
31
+ else
32
+ {
33
+ ConsoleWriteError(hr, CONSOLE_COLOR_RED, "Unknown command: %ls", argv[1]);
34
+ }
35
+
36
+ ConsoleUninitialize();
37
+ return HRESULT_CODE(hr);
38
+}
src/wixnative/wixnative.vcxproj
new
+80
@@ -0,0 +1,80 @@
1
+<?xml version="1.0" encoding="utf-8"?>
2
+<!-- 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. -->
3
+
4
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5
+ <Import Project="..\..\packages\WixToolset.DUtil.4.0.3\build\WixToolset.DUtil.props" Condition="Exists('..\..\packages\WixToolset.DUtil.4.0.3\build\WixToolset.DUtil.props')" />
6
+
7
+ <ItemGroup Label="ProjectConfigurations">
8
+ <ProjectConfiguration Include="Debug|Win32">
9
+ <Configuration>Debug</Configuration>
10
+ <Platform>Win32</Platform>
11
+ </ProjectConfiguration>
12
+ <ProjectConfiguration Include="Release|Win32">
13
+ <Configuration>Release</Configuration>
14
+ <Platform>Win32</Platform>
15
+ </ProjectConfiguration>
16
+ <ProjectConfiguration Include="Debug|x64">
17
+ <Configuration>Debug</Configuration>
18
+ <Platform>x64</Platform>
19
+ </ProjectConfiguration>
20
+ <ProjectConfiguration Include="Release|x64">
21
+ <Configuration>Release</Configuration>
22
+ <Platform>x64</Platform>
23
+ </ProjectConfiguration>
24
+ </ItemGroup>
25
+
26
+ <PropertyGroup Label="Globals">
27
+ <ProjectGuid>{8497EC72-B8D0-4272-A9D0-7E9D871CEFBF}</ProjectGuid>
28
+ <ConfigurationType>Application</ConfigurationType>
29
+ <ProjectSubSystem>Console</ProjectSubSystem>
30
+ <TargetName>wixnative</TargetName>
31
+ <PlatformToolset>v141</PlatformToolset>
32
+ <CharacterSet>Unicode</CharacterSet>
33
+ <Description>Native component of WixToolset.Core</Description>
34
+ </PropertyGroup>
35
+
36
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
37
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
38
+
39
+ <ImportGroup Label="ExtensionSettings">
40
+ </ImportGroup>
41
+
42
+ <ImportGroup Label="Shared">
43
+ </ImportGroup>
44
+
45
+ <PropertyGroup>
46
+ <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib</ProjectAdditionalLinkLibraries>
47
+ </PropertyGroup>
48
+
49
+ <ItemGroup>
50
+ <ClCompile Include="wixnative.cpp">
51
+ <!-- turn off deprecation warning -->
52
+ <DisableSpecificWarnings>4996</DisableSpecificWarnings>
53
+ </ClCompile>
54
+ <ClCompile Include="precomp.cpp">
55
+ <PrecompiledHeader>Create</PrecompiledHeader>
56
+ </ClCompile>
57
+ <ClCompile Include="enumcab.cpp"/>
58
+ <ClCompile Include="extractcab.cpp"/>
59
+ <ClCompile Include="resetacls.cpp"/>
60
+ <ClCompile Include="smartcab.cpp"/>
61
+ </ItemGroup>
62
+
63
+ <ItemGroup>
64
+ <ClInclude Include="precomp.h" />
65
+ </ItemGroup>
66
+
67
+ <ItemGroup>
68
+ <None Include="packages.config" />
69
+ </ItemGroup>
70
+
71
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
72
+ <Import Project="..\..\packages\Nerdbank.GitVersioning.2.0.41\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.2.0.41\build\Nerdbank.GitVersioning.targets')" />
73
+ <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
74
+ <PropertyGroup>
75
+ <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
76
+ </PropertyGroup>
77
+ <Error Condition="!Exists('..\..\packages\WixToolset.DUtil.4.0.3\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\WixToolset.DUtil.4.0.3\build\WixToolset.DUtil.props'))" />
78
+ <Error Condition="!Exists('..\..\packages\Nerdbank.GitVersioning.2.0.41\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.2.0.41\build\Nerdbank.GitVersioning.targets'))" />
79
+ </Target>
80
+</Project>