// 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.
namespace WixToolset.Msmq
{
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using WixToolset.Data;
using WixToolset.Extensibility;
using WixToolset.Extensibility.Data;
using WixToolset.Msmq.Symbols;
///
/// The compiler for the WiX Toolset MSMQ Extension.
///
public sealed class MsmqCompiler : BaseCompilerExtension
{
public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/msmq";
///
///
///
public enum MqiMessageQueueAttributes
{
Authenticate = (1 << 0),
Journal = (1 << 1),
Transactional = (1 << 2)
}
///
///
///
public enum MqiMessageQueuePrivacyLevel
{
None = 0,
Optional = 1,
Body = 2
}
///
///
///
public enum MqiMessageQueuePermission
{
DeleteMessage = (1 << 0),
PeekMessage = (1 << 1),
WriteMessage = (1 << 2),
DeleteJournalMessage = (1 << 3),
SetQueueProperties = (1 << 4),
GetQueueProperties = (1 << 5),
DeleteQueue = (1 << 6),
GetQueuePermissions = (1 << 7),
ChangeQueuePermissions = (1 << 8),
TakeQueueOwnership = (1 << 9),
ReceiveMessage = (1 << 10),
ReceiveJournalMessage = (1 << 11),
QueueGenericRead = (1 << 12),
QueueGenericWrite = (1 << 13),
QueueGenericExecute = (1 << 14),
QueueGenericAll = (1 << 15)
}
///
/// Processes an element for the Compiler.
///
/// Parent element of element to process.
/// Element to process.
/// Extra information about the context in which this element is being parsed.
public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary context)
{
switch (parentElement.Name.LocalName)
{
case "Component":
var componentId = context["ComponentId"];
switch (element.Name.LocalName)
{
case "MessageQueue":
this.ParseMessageQueueElement(intermediate, section, element, componentId);
break;
case "MessageQueuePermission":
this.ParseMessageQueuePermissionElement(intermediate, section, element, componentId, null);
break;
default:
this.ParseHelper.UnexpectedElement(parentElement, element);
break;
}
break;
default:
this.ParseHelper.UnexpectedElement(parentElement, element);
break;
}
}
///
/// Parses an MSMQ message queue element.
///
/// Element to parse.
/// Identifier of parent component.
private void ParseMessageQueueElement(Intermediate intermediate, IntermediateSection section, XElement node, string componentId)
{
var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node);
Identifier id = null;
var basePriority = CompilerConstants.IntegerNotSet;
var journalQuota = CompilerConstants.IntegerNotSet;
string label = null;
string multicastAddress = null;
string pathName = null;
var privLevel = CompilerConstants.IntegerNotSet;
var quota = CompilerConstants.IntegerNotSet;
string serviceTypeGuid = null;
int attributes = 0;
foreach (var attrib in node.Attributes())
{
if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
{
switch (attrib.Name.LocalName)
{
case "Id":
id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
break;
case "Authenticate":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
attributes |= (int)MqiMessageQueueAttributes.Authenticate;
}
else
{
attributes &= ~(int)MqiMessageQueueAttributes.Authenticate;
}
break;
case "BasePriority":
basePriority = this.ParseHelper.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
break;
case "Journal":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
attributes |= (int)MqiMessageQueueAttributes.Journal;
}
else
{
attributes &= ~(int)MqiMessageQueueAttributes.Journal;
}
break;
case "JournalQuota":
journalQuota = this.ParseHelper.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
break;
case "Label":
label = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "MulticastAddress":
multicastAddress = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "PathName":
pathName = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
break;
case "PrivLevel":
var privLevelAttr = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
switch (privLevelAttr)
{
case "none":
privLevel = (int)MqiMessageQueuePrivacyLevel.None;
break;
case "optional":
privLevel = (int)MqiMessageQueuePrivacyLevel.Optional;
break;
case "body":
privLevel = (int)MqiMessageQueuePrivacyLevel.Body;
break;
default:
this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "MessageQueue", "PrivLevel", privLevelAttr, "none", "body", "optional"));
break;
}
break;
case "Quota":
quota = this.ParseHelper.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
break;
case "Transactional":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
attributes |= (int)MqiMessageQueueAttributes.Transactional;
}
else
{
attributes &= ~(int)MqiMessageQueueAttributes.Transactional;
}
break;
case "ServiceTypeGuid":
serviceTypeGuid = this.TryFormatGuidValue(this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib));
break;
default:
this.ParseHelper.UnexpectedAttribute(node, attrib);
break;
}
}
else
{
this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib);
}
}
foreach (var child in node.Elements())
{
if (this.Namespace == child.Name.Namespace)
{
switch (child.Name.LocalName)
{
case "MessageQueuePermission":
this.ParseMessageQueuePermissionElement(intermediate, section, child, componentId, id?.Id);
break;
default:
this.ParseHelper.UnexpectedElement(node, child);
break;
}
}
else
{
this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, node, child);
}
}
var symbol = section.AddSymbol(new MessageQueueSymbol(sourceLineNumbers, id)
{
ComponentRef = componentId,
Label = label,
MulticastAddress = multicastAddress,
PathName = pathName,
ServiceTypeGuid = serviceTypeGuid,
Attributes = attributes,
});
if (CompilerConstants.IntegerNotSet != basePriority)
{
symbol.BasePriority = basePriority;
}
if (CompilerConstants.IntegerNotSet != journalQuota)
{
symbol.JournalQuota = journalQuota;
}
if (CompilerConstants.IntegerNotSet != privLevel)
{
symbol.PrivLevel = privLevel;
}
if (CompilerConstants.IntegerNotSet != quota)
{
symbol.Quota = quota;
}
this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4MessageQueuingInstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4MessageQueuingUninstall", this.Context.Platform, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
}
///
/// Parses an MSMQ message queue permission element.
///
/// Element to parse.
/// Identifier of parent component.
/// Optional identifier of parent message queue.
private void ParseMessageQueuePermissionElement(Intermediate intermediate, IntermediateSection section, XElement node, string componentId, string messageQueueId)
{
var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(node);
Identifier id = null;
string user = null;
string group = null;
int permissions = 0;
foreach (var attrib in node.Attributes())
{
if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
{
switch (attrib.Name.LocalName)
{
case "Id":
id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
break;
case "MessageQueue":
if (null != messageQueueId)
{
this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
}
messageQueueId = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, MsmqSymbolDefinitions.MessageQueue, messageQueueId);
break;
case "User":
if (null != group)
{
this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "User", "Group"));
}
user = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "User", user);
break;
case "Group":
if (null != user)
{
this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Group", "User"));
}
group = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, "Group", group);
break;
case "DeleteMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.DeleteMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.DeleteMessage;
}
break;
case "PeekMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.PeekMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.PeekMessage;
}
break;
case "WriteMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.WriteMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.WriteMessage;
}
break;
case "DeleteJournalMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.DeleteJournalMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.DeleteJournalMessage;
}
break;
case "SetQueueProperties":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.SetQueueProperties;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.SetQueueProperties;
}
break;
case "GetQueueProperties":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.GetQueueProperties;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.GetQueueProperties;
}
break;
case "DeleteQueue":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.DeleteQueue;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.DeleteQueue;
}
break;
case "GetQueuePermissions":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.GetQueuePermissions;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.GetQueuePermissions;
}
break;
case "ChangeQueuePermissions":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.ChangeQueuePermissions;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.ChangeQueuePermissions;
}
break;
case "TakeQueueOwnership":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.TakeQueueOwnership;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.TakeQueueOwnership;
}
break;
case "ReceiveMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.ReceiveMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.ReceiveMessage;
}
break;
case "ReceiveJournalMessage":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.ReceiveJournalMessage;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.ReceiveJournalMessage;
}
break;
case "QueueGenericRead":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.QueueGenericRead;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.QueueGenericRead;
}
break;
case "QueueGenericWrite":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.QueueGenericWrite;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.QueueGenericWrite;
}
break;
case "QueueGenericExecute":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.QueueGenericExecute;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.QueueGenericExecute;
}
break;
case "QueueGenericAll":
if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib))
{
permissions |= (int)MqiMessageQueuePermission.QueueGenericAll;
}
else
{
permissions &= ~(int)MqiMessageQueuePermission.QueueGenericAll;
}
break;
default:
this.ParseHelper.UnexpectedAttribute(node, attrib);
break;
}
}
else
{
this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, node, attrib);
}
}
if (null == id)
{
id = this.ParseHelper.CreateIdentifier("mqp", componentId, messageQueueId, user, group);
}
if (null == messageQueueId)
{
this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MessageQueue"));
}
if (null == user && null == group)
{
this.Messaging.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "User", "Group"));
}
this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, node);
if (null != user)
{
section.AddSymbol(new MessageQueueUserPermissionSymbol(sourceLineNumbers, id)
{
ComponentRef = componentId,
MessageQueueRef = messageQueueId,
UserRef = user,
Permissions = permissions,
});
}
if (null != group)
{
section.AddSymbol(new MessageQueueGroupPermissionSymbol(sourceLineNumbers, id)
{
ComponentRef = componentId,
MessageQueueRef = messageQueueId,
GroupRef = group,
Permissions = permissions,
});
}
}
///
/// Attempts to parse the input value as a GUID, and in case the value is a valid
/// GUID returnes it in the format "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}".
///
///
///
string TryFormatGuidValue(string val)
{
if (!Guid.TryParse(val, out var guid))
{
return val;
}
return guid.ToString("B").ToUpper();
}
}
}