main
cs 330 lines 18.1 KB
Raw
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.VisualStudio
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13
14 /// <summary>
15 /// The compiler for the WiX Toolset Visual Studio Extension.
16 /// </summary>
17 public sealed class VSCompiler : BaseCompilerExtension
18 {
19 internal const int MsidbCustomActionTypeExe = 0x00000002; // Target = command line args
20 internal const int MsidbCustomActionTypeProperty = 0x00000030; // Source = full path to executable
21 internal const int MsidbCustomActionTypeContinue = 0x00000040; // ignore action return status; continue running
22 internal const int MsidbCustomActionTypeRollback = 0x00000100; // in conjunction with InScript: queue in Rollback script
23 internal const int MsidbCustomActionTypeInScript = 0x00000400; // queue for execution within script
24 internal const int MsidbCustomActionTypeNoImpersonate = 0x00000800; // queue for not impersonating
25
26 public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/vs";
27
28 public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context)
29 {
30 switch (parentElement.Name.LocalName)
31 {
32 case "Component":
33 switch (element.Name.LocalName)
34 {
35 case "VsixPackage":
36 this.ParseVsixPackageElement(intermediate, section, element, context["ComponentId"], null);
37 break;
38 default:
39 this.ParseHelper.UnexpectedElement(parentElement, element);
40 break;
41 }
42 break;
43 case "File":
44 switch (element.Name.LocalName)
45 {
46 case "VsixPackage":
47 this.ParseVsixPackageElement(intermediate, section, element, context["ComponentId"], context["FileId"]);
48 break;
49 default:
50 this.ParseHelper.UnexpectedElement(parentElement, element);
51 break;
52 }
53 break;
54 case "Fragment":
55 case "Module":
56 case "Package":
57 switch (element.Name.LocalName)
58 {
59 case "FindVisualStudio":
60 this.ParseFindVisualStudioElement(intermediate, section, element);
61 break;
62 default:
63 this.ParseHelper.UnexpectedElement(parentElement, element);
64 break;
65 }
66 break;
67 default:
68 this.ParseHelper.UnexpectedElement(parentElement, element);
69 break;
70 }
71 }
72
73 private void ParseFindVisualStudioElement(Intermediate intermediate, IntermediateSection section, XElement element)
74 {
75 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
76
77 this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
78
79 this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4VSFindInstances", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
80 }
81
82 private void ParseVsixPackageElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId, string fileId)
83 {
84 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
85 var propertyId = "VS_VSIX_INSTALLER_PATH";
86 string packageId = null;
87 var permanent = YesNoType.NotSet;
88 string target = null;
89 string targetVersion = null;
90 var vital = YesNoType.NotSet;
91
92 foreach (var attrib in element.Attributes())
93 {
94 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
95 {
96 switch (attrib.Name.LocalName)
97 {
98 case "File":
99 if (String.IsNullOrEmpty(fileId))
100 {
101 fileId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
102 }
103 else
104 {
105 this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", "File"));
106 }
107 break;
108 case "PackageId":
109 packageId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
110 break;
111 case "Permanent":
112 permanent = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
113 break;
114 case "Target":
115 target = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
116 switch (target.ToLowerInvariant())
117 {
118 case "integrated":
119 case "integratedshell":
120 target = "IntegratedShell";
121 break;
122 case "professional":
123 target = "Pro";
124 break;
125 case "premium":
126 target = "Premium";
127 break;
128 case "ultimate":
129 target = "Ultimate";
130 break;
131 case "vbexpress":
132 target = "VBExpress";
133 break;
134 case "vcexpress":
135 target = "VCExpress";
136 break;
137 case "vcsexpress":
138 target = "VCSExpress";
139 break;
140 case "vwdexpress":
141 target = "VWDExpress";
142 break;
143 }
144 break;
145 case "TargetVersion":
146 targetVersion = this.ParseHelper.GetAttributeVersionValue(sourceLineNumbers, attrib);
147 break;
148 case "Vital":
149 vital = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib);
150 break;
151 case "VsixInstallerPathProperty":
152 propertyId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
153 break;
154 default:
155 this.ParseHelper.UnexpectedAttribute(element, attrib);
156 break;
157 }
158 }
159 else
160 {
161 this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
162 }
163 }
164
165 if (String.IsNullOrEmpty(fileId))
166 {
167 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "File"));
168 }
169
170 if (String.IsNullOrEmpty(packageId))
171 {
172 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "PackageId"));
173 }
174
175 if (!String.IsNullOrEmpty(target) && String.IsNullOrEmpty(targetVersion))
176 {
177 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "TargetVersion", "Target"));
178 }
179 else if (String.IsNullOrEmpty(target) && !String.IsNullOrEmpty(targetVersion))
180 {
181 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Target", "TargetVersion"));
182 }
183
184 this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
185
186 if (!this.Messaging.EncounteredError)
187 {
188 // Ensure there is a reference to the AppSearch Property that will find the VsixInstaller.exe.
189 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Property, propertyId);
190
191 // Ensure there is a reference to the package file (even if we are a child under it).
192 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.File, fileId);
193
194 var cmdlinePrefix = "/q ";
195
196 if (!String.IsNullOrEmpty(target))
197 {
198 cmdlinePrefix = String.Format("{0} /skuName:{1} /skuVersion:{2}", cmdlinePrefix, target, targetVersion);
199 }
200
201 var installAfter = "WriteRegistryValues"; // by default, come after the registry key registration.
202
203 var installNamePerUser = this.ParseHelper.CreateIdentifier("viu", componentId, fileId, "per-user", target, targetVersion);
204 var installNamePerMachine = this.ParseHelper.CreateIdentifier("vim", componentId, fileId, "per-machine", target, targetVersion);
205 var installCmdLinePerUser = String.Format("{0} \"[#{1}]\"", cmdlinePrefix, fileId);
206 var installCmdLinePerMachine = String.Concat(installCmdLinePerUser, " /admin");
207 var installConditionPerUser = String.Format("NOT ALLUSERS AND ${0}=3", componentId); // only execute if the Component being installed.
208 var installConditionPerMachine = String.Format("ALLUSERS AND ${0}=3", componentId); // only execute if the Component being installed.
209 var installPerUserCA = new CustomActionSymbol(sourceLineNumbers, installNamePerUser)
210 {
211 ExecutionType = CustomActionExecutionType.Deferred,
212 Impersonate = true,
213 };
214 var installPerMachineCA = new CustomActionSymbol(sourceLineNumbers, installNamePerMachine)
215 {
216 ExecutionType = CustomActionExecutionType.Deferred,
217 Impersonate = false,
218 };
219
220 // If the package is not vital, mark the install action as continue.
221 if (vital == YesNoType.No)
222 {
223 installPerUserCA.IgnoreResult = true;
224 installPerMachineCA.IgnoreResult = true;
225 }
226 else // the package is vital so ensure there is a rollback action scheduled.
227 {
228 var rollbackNamePerUser = this.ParseHelper.CreateIdentifier("vru", componentId, fileId, "per-user", target, targetVersion);
229 var rollbackNamePerMachine = this.ParseHelper.CreateIdentifier("vrm", componentId, fileId, "per-machine", target, targetVersion);
230 var rollbackCmdLinePerUser = String.Concat(cmdlinePrefix, " /u:\"", packageId, "\"");
231 var rollbackCmdLinePerMachine = String.Concat(rollbackCmdLinePerUser, " /admin");
232 var rollbackConditionPerUser = String.Format("NOT ALLUSERS AND NOT Installed AND ${0}=2 AND ?{0}>2", componentId); // NOT Installed && Component being installed but not installed already.
233 var rollbackConditionPerMachine = String.Format("ALLUSERS AND NOT Installed AND ${0}=2 AND ?{0}>2", componentId); // NOT Installed && Component being installed but not installed already.
234 var rollbackPerUserCA = new CustomActionSymbol(sourceLineNumbers, rollbackNamePerUser)
235 {
236 ExecutionType = CustomActionExecutionType.Rollback,
237 IgnoreResult = true,
238 Impersonate = true,
239 };
240 var rollbackPerMachineCA = new CustomActionSymbol(sourceLineNumbers, rollbackNamePerMachine)
241 {
242 ExecutionType = CustomActionExecutionType.Rollback,
243 IgnoreResult = true,
244 Impersonate = false,
245 };
246
247 this.SchedulePropertyExeAction(section, sourceLineNumbers, rollbackNamePerUser, propertyId, rollbackCmdLinePerUser, rollbackPerUserCA, rollbackConditionPerUser, null, installAfter);
248 this.SchedulePropertyExeAction(section, sourceLineNumbers, rollbackNamePerMachine, propertyId, rollbackCmdLinePerMachine, rollbackPerMachineCA, rollbackConditionPerMachine, null, rollbackNamePerUser.Id);
249
250 installAfter = rollbackNamePerMachine.Id;
251 }
252
253 this.SchedulePropertyExeAction(section, sourceLineNumbers, installNamePerUser, propertyId, installCmdLinePerUser, installPerUserCA, installConditionPerUser, null, installAfter);
254 this.SchedulePropertyExeAction(section, sourceLineNumbers, installNamePerMachine, propertyId, installCmdLinePerMachine, installPerMachineCA, installConditionPerMachine, null, installNamePerUser.Id);
255
256 // If not permanent, schedule the uninstall custom action.
257 if (permanent != YesNoType.Yes)
258 {
259 var uninstallNamePerUser = this.ParseHelper.CreateIdentifier("vuu", componentId, fileId, "per-user", target ?? String.Empty, targetVersion ?? String.Empty);
260 var uninstallNamePerMachine = this.ParseHelper.CreateIdentifier("vum", componentId, fileId, "per-machine", target ?? String.Empty, targetVersion ?? String.Empty);
261 var uninstallCmdLinePerUser = String.Concat(cmdlinePrefix, " /u:\"", packageId, "\"");
262 var uninstallCmdLinePerMachine = String.Concat(uninstallCmdLinePerUser, " /admin");
263 var uninstallConditionPerUser = String.Format("NOT ALLUSERS AND ${0}=2 AND ?{0}>2", componentId); // Only execute if component is being uninstalled.
264 var uninstallConditionPerMachine = String.Format("ALLUSERS AND ${0}=2 AND ?{0}>2", componentId); // Only execute if component is being uninstalled.
265 var uninstallPerUserCA = new CustomActionSymbol(sourceLineNumbers, uninstallNamePerUser)
266 {
267 ExecutionType = CustomActionExecutionType.Deferred,
268 IgnoreResult = true,
269 Impersonate = true,
270 };
271 var uninstallPerMachineCA = new CustomActionSymbol(sourceLineNumbers, uninstallNamePerMachine)
272 {
273 ExecutionType = CustomActionExecutionType.Deferred,
274 IgnoreResult = true,
275 Impersonate = false,
276 };
277
278 this.SchedulePropertyExeAction(section, sourceLineNumbers, uninstallNamePerUser, propertyId, uninstallCmdLinePerUser, uninstallPerUserCA, uninstallConditionPerUser, "InstallFinalize", null);
279 this.SchedulePropertyExeAction(section, sourceLineNumbers, uninstallNamePerMachine, propertyId, uninstallCmdLinePerMachine, uninstallPerMachineCA, uninstallConditionPerMachine, "InstallFinalize", null);
280 }
281 }
282 }
283
284 private void SchedulePropertyExeAction(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier name, string source, string cmdline, CustomActionSymbol caTemplate, string condition, string beforeAction, string afterAction)
285 {
286 const SequenceTable sequence = SequenceTable.InstallExecuteSequence;
287
288 caTemplate.SourceType = CustomActionSourceType.Property;
289 caTemplate.Source = source;
290 caTemplate.TargetType = CustomActionTargetType.Exe;
291 caTemplate.Target = cmdline;
292 section.AddSymbol(caTemplate);
293
294 section.AddSymbol(new WixActionSymbol(sourceLineNumbers, new Identifier(name.Access, sequence, name.Id))
295 {
296 SequenceTable = SequenceTable.InstallExecuteSequence,
297 Action = name.Id,
298 Condition = condition,
299 // no explicit sequence
300 Before = beforeAction,
301 After = afterAction,
302 Overridable = false,
303 });
304
305 if (null != beforeAction)
306 {
307 if (WindowsInstallerStandard.IsStandardAction(beforeAction))
308 {
309 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction);
310 }
311 else
312 {
313 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction);
314 }
315 }
316
317 if (null != afterAction)
318 {
319 if (WindowsInstallerStandard.IsStandardAction(afterAction))
320 {
321 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction);
322 }
323 else
324 {
325 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction);
326 }
327 }
328 }
329 }
330 }