If you read my last post, Creating Audiences via STSADM, then you know that I’ve been working on a project which requires me to be able to script out the creation of audiences via STSADM. My last post covered the creation of the audience itself, but an audience with no rules isn’t all that useful, so for this post I’ll be covering my next custom command, gl-addaudiencerule, which enables you to add complex rules to an audience.
I’ll reiterate a couple of things regarding creating rules from my last post. First off, when you create rules via the browser you are limited to just simple rules – in other words, you may have multiple rules but the boolean logic is limited to all rules matching or any rules matching – there is no combination or complex boolean logic with grouping. This is not the case if you create the rules programmatically – by programmatically creating the rules we can use grouping (up to 3 levels deep) and any combination of boolean logic. The catch is that as soon as you add any complex rules to an audience you will now no longer be able to manage that audience via the browser – you’ll still be able to compile the audience and view memberships but you won’t be able to manage or even view any rules associated with the audience and you won’t be able to delete the audience. I created two more commands that allow you to see the rules and delete the audience via STSADM but I’ll talk about them in follow-up posts.
Microsoft took an interesting approach to storing the rules – they basically use an ArrayList of objects of type AudienceRuleComponent. Each object represents a part of the rule, including the the parentheses and logic operators (AND, OR). So a rule like the following would consist of 7 objects:
(Department == "IT" OR Reports Under == "domain\glapointe") AND IsContractor == false
The above would be broken down into objects in the following fashion:
- new AudienceRuleComponent(null, "(", null);
- new AudienceRuleComponent("Department", "=", "IT");
- new AudienceRuleComponent(null, "OR", null);
- new AudienceRuleComponent("Everyone", "Reports Under", "domain\glapointe");
- new AudienceRuleComponent(null, ")", null);
- new AudienceRuleComponent(null, "AND", null);
- new AudienceRuleComponent("IsContractor", "=", "false");
The objects created above would be added to the rules collection array list in the order listed. One thing you may have noticed above is that the field for the "reports under" operation is "Everyone" – the field for the "member of" operation is actually "DL". It’s important to note that if you change the rules you must reassign the AudienceRules property rather than manipulate the items via the property:
- audience.AudienceRules.Add(new AudienceRuleComponent(null, "(", null)); // This will not work as the property will not be marked as dirty and will therefore not be saved when Commit is called.
- ArrayList rules = audience.AudienceRules;
rules..Add(new AudienceRuleComponent(null, "(", null));
audience.AudienceRules = rules; // This assignment marks the audience rules as dirty and will thus be saved.
The way I decided to handle the creation of these rules was to allow a simple XML structure to be passed into the command either directly via a parameter or indirectly by passing in a file containing the rules. The structure of the XML is similar to the structure of the above code – you create one or more <rule /> elements which are wrapped in a <rules /> element. The <rule /> element contains one required attribute, "op", and two optional (depending on the operation) attributes, "field" and "value". Grouping operations do not require the field and value attributes and member of and reports under operations do not require the field attribute. Here’s an example of the above:
1: <rules>2: <rule op="(" />3: <rule field="Department" op="=" value="IT" />4: <rule op="or" />5: <rule op="reports under" value="domain\glapointe" />6: <rule op=")" />7: <rule op="and" />8: <rule field="IsContractor" op="=" value="false" />9: </rules>
1: case "member of":
2: field = "DL";
3: val = rule.GetAttribute("value");
4: ArrayList path = AudienceManager.GetADsPath(val);
5: if (path.Count == 0)
6: throw new ArgumentException(string.Format("Security group or distribution list was not found: {0}", val));
7: val = ((string)path[0]).Replace("LDAP://", "");
8: break;
1: #if MOSS
2: using System;
3: using System.Collections;
4: using System.Collections.Specialized;
5: using System.IO;
6: using System.Text;
7: using System.Xml;
8: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
9: using Lapointe.SharePoint.STSADM.Commands.SPValidators;
10: using Microsoft.Office.Server;
11: using Microsoft.Office.Server.Audience;
12: using Microsoft.Office.Server.Search.Administration;
13: using Microsoft.SharePoint;
14:
15: namespace Lapointe.SharePoint.STSADM.Commands.Audiences
16: {
17: public class AddAudienceRule : SPOperation
18: {
19: public enum AppendOp
20: {
21: AND, OR
22: }
23:
24: /// <summary>
25: /// Initializes a new instance of the <see cref="AddAudienceRule"/> class.
26: /// </summary>
27: public AddAudienceRule()
28: {
29: SPEnumValidator appendOpValidator = new SPEnumValidator(typeof(AppendOp));
30:
31: SPParamCollection parameters = new SPParamCollection();
32: parameters.Add(new SPParam("name", "n", true, null, new SPNonEmptyValidator()));
33: parameters.Add(new SPParam("ssp", "ssp", false, null, new SPNonEmptyValidator()));
34: parameters.Add(new SPParam("rules", "r", false, null, new SPNonEmptyValidator()));
35: parameters.Add(new SPParam("rulesfile", "rf", false, null, new SPFileExistsValidator()));
36: parameters.Add(new SPParam("clear", "cl"));
37: parameters.Add(new SPParam("compile", "co"));
38: parameters.Add(new SPParam("groupexisting", "group"));
39: parameters.Add(new SPParam("appendop", "op", false, "and", appendOpValidator));
40:
41: StringBuilder sb = new StringBuilder();
42: sb.Append("\r\n\r\nAdds simple or complex rules to an existing audience. The rules XML should be in the following format: ");
43: sb.Append("<rules><rule op='' field='' value='' /></rules>\r\n");
44: sb.Append("Values for the \"op\" attribute can be any of \"=,>,>=,<,<=,<>,Contains,Not contains,Reports Under,Member Of,AND,OR,(,)\"\r\n");
45: sb.Append("The \"field\" attribute is not required if \"op\" is any of \"Reports Under,Member Of,AND,OR,(,)\"\r\n");
46: sb.Append("The \"value\" attribute is not required if \"op\" is any of \"AND,OR,(,)\"\r\n");
47: sb.Append("Note that if your rules contain any grouping or mixed logic then you will not be able to manage the rule via the browser.\r\n");
48: sb.Append("Example: <rules><rule op='Member of' value='sales department' /><rule op='AND' /><rule op='Contains' field='Department' value='Sales' /></rules>");
49: sb.Append("\r\n\r\nParameters:");
50: sb.Append("\r\n\t-name <audience name>");
51: sb.Append("\r\n\t-rules <rules xml> | -rulesfile <xml file containing the rules>");
52: sb.Append("\r\n\t[-ssp <SSP name>]");
53: sb.Append("\r\n\t[-clear (clear existing rules)]");
54: sb.Append("\r\n\t[-compile]");
55: sb.Append("\r\n\t[-groupexisting (wraps any existing rules in parantheses)]");
56: sb.Append("\r\n\t[-appendop <and (default) | or> (operator used to append to existing rules)]");
57: Init(parameters, sb.ToString());
58: }
59:
60: /// <summary>
61: /// Gets the help message.
62: /// </summary>
63: /// <param name="command">The command.</param>
64: /// <returns></returns>
65: public override string GetHelpMessage(string command)
66: {
67: return HelpMessage;
68: }
69:
70: /// <summary>
71: /// Executes the specified command.
72: /// </summary>
73: /// <param name="command">The command.</param>
74: /// <param name="keyValues">The key values.</param>
75: /// <param name="output">The output.</param>
76: /// <returns></returns>
77: public override int Execute(string command, StringDictionary keyValues, out string output)
78: {
79: output = string.Empty;
80:
81: string rules;
82: if (Params["rules"].UserTypedIn)
83: rules = Params["rules"].Value;
84: else
85: rules = File.ReadAllText(Params["rulesfile"].Value);
86:
87: AddRules(Params["ssp"].Value,
88: Params["name"].Value,
89: rules,
90: Params["clear"].UserTypedIn,
91: Params["compile"].UserTypedIn,
92: Params["groupexisting"].UserTypedIn,
93: (AppendOp)Enum.Parse(typeof(AppendOp), Params["appendop"].Value, true));
94:
95: return OUTPUT_SUCCESS;
96: }
97:
98: /// <summary>
99: /// Validates the specified key values.
100: /// </summary>
101: /// <param name="keyValues">The key values.</param>
102: public override void Validate(StringDictionary keyValues)
103: {
104: SPBinaryParameterValidator.Validate("rules", Params["rules"].Value, "rulesfile", Params["rulesfile"].Value);
105:
106: if (Params["clear"].UserTypedIn && (Params["appendop"].UserTypedIn || Params["groupexisting"].UserTypedIn))
107: throw new SPSyntaxException("The -clear parameter cannot be used with the -appendop or -groupexisting parameters.");
108:
109: base.Validate(keyValues);
110: }
111:
112: /// <summary>
113: /// Adds the rules.
114: /// </summary>
115: /// <param name="sspName">Name of the SSP.</param>
116: /// <param name="audienceName">Name of the audience.</param>
117: /// <param name="rules">The rules.</param>
118: /// <param name="clearExistingRules">if set to <c>true</c> [clear existing rules].</param>
119: /// <param name="compile">if set to <c>true</c> [compile].</param>
120: /// <param name="groupExisting">if set to <c>true</c> [group existing].</param>
121: /// <param name="appendOp">The append op.</param>
122: public static void AddRules(string sspName, string audienceName, string rules, bool clearExistingRules, bool compile, bool groupExisting, AppendOp appendOp)
123: {
124: ServerContext context;
125: if (string.IsNullOrEmpty(sspName))
126: context = ServerContext.Default;
127: else
128: context = ServerContext.GetContext(sspName);
129:
130: AudienceManager manager = new AudienceManager(context);
131:
132: if (!manager.Audiences.AudienceExist(audienceName))
133: {
134: throw new SPException("Audience name does not exist");
135: }
136:
137: Audience audience = manager.Audiences[audienceName];
138: /*
139: Operator Need left and right operands (not a group operator)
140: = Yes
141: > Yes
142: >= Yes
143: < Yes
144: <= Yes
145: Contains Yes
146: Reports Under Yes (Left operand must be 'Everyone')
147: <> Yes
148: Not contains Yes
149: AND No
150: OR No
151: ( No
152: ) No
153: Member Of Yes (Left operand must be 'DL')
154: */
155: XmlDocument rulesDoc = new XmlDocument();
156: rulesDoc.LoadXml(rules);
157:
158: ArrayList audienceRules = audience.AudienceRules;
159: bool ruleListNotEmpty = false;
160:
161: if (audienceRules == null || clearExistingRules)
162: audienceRules = new ArrayList();
163: else
164: ruleListNotEmpty = true;
165:
166: //if the rule is not emply, start with a group operator 'AND' to append
167: if (ruleListNotEmpty)
168: {
169: if (groupExisting)
170: {
171: audienceRules.Insert(0, new AudienceRuleComponent(null, "(", null));
172: audienceRules.Add(new AudienceRuleComponent(null, ")", null));
173: }
174:
175: audienceRules.Add(new AudienceRuleComponent(null, appendOp.ToString(), null));
176: }
177:
178: if (rulesDoc.SelectNodes("//rule") == null || rulesDoc.SelectNodes("//rule").Count == 0)
179: throw new ArgumentException("No rules were supplied.");
180:
181: foreach (XmlElement rule in rulesDoc.SelectNodes("//rule"))
182: {
183: string op = rule.GetAttribute("op").ToLowerInvariant();
184: string field = null;
185: string val = null;
186: bool valIsRequired = true;
187: bool fieldIsRequired = false;
188:
189: switch (op)
190: {
191: case "=":
192: case ">":
193: case ">=":
194: case "<":
195: case "<=":
196: case "contains":
197: case "<>":
198: case "not contains":
199: field = rule.GetAttribute("field");
200: val = rule.GetAttribute("value");
201: fieldIsRequired = true;
202: break;
203: case "reports under":
204: field = "Everyone";
205: val = rule.GetAttribute("value");
206: break;
207: case "member of":
208: field = "DL";
209: val = rule.GetAttribute("value");
210: ArrayList path = AudienceManager.GetADsPath(val);
211: if (path.Count == 0)
212: throw new ArgumentException(string.Format("Security group or distribution list was not found: {0}", val));
213: val = ((string)path[0]).Replace("LDAP://", "");
214: break;
215: case "and":
216: case "or":
217: case "(":
218: case ")":
219: valIsRequired = false;
220: break;
221: default:
222: throw new ArgumentException(string.Format("Rule operator is invalid: {0}", rule.GetAttribute("op")));
223: }
224: if (valIsRequired && string.IsNullOrEmpty(val))
225: throw new ArgumentNullException(string.Format("Rule value attribute is missing or invalid: {0}", rule.GetAttribute("value")));
226:
227: if (fieldIsRequired && string.IsNullOrEmpty(field))
228: throw new ArgumentNullException(string.Format("Rule field attribute is missing or invalid: {0}", rule.GetAttribute("field")));
229:
230: AudienceRuleComponent r0 = new AudienceRuleComponent(field, op, val);
231: audienceRules.Add(r0);
232: }
233:
234: audience.AudienceRules = audienceRules;
235: audience.Commit();
236: if (compile)
237: CompileAudience(context, audience.AudienceName);
238: }
239:
240: /// <summary>
241: /// Compiles the audience.
242: /// </summary>
243: /// <param name="context">The context.</param>
244: /// <param name="audienceName">Name of the audience.</param>
245: public static void CompileAudience(ServerContext context, string audienceName)
246: {
247: SearchContext searchContext = SearchContext.GetContext(context);
248:
249: string[] args = new string[4];
250: args[0] = searchContext.Name;
251: args[1] = "1"; //"1" = start job, "0" = stop job
252: args[2] = "1"; //"1" = full compilation, "0" = incremental compilation (optional, default = 0)
253: args[3] = audienceName;
254:
255: AudienceJob.RunAudienceJob(args);
256: }
257: }
258: }
259: #endif
The help for the command is shown below:
C:\>stsadm -help gl-addaudiencerule stsadm -o gl-addaudiencerule Adds simple or complex rules to an existing audience. The rules XML should be in the following format: <rules><rule op='' field='' value='' /></rules> Values for the "op" attribute can be any of "=,>,>=,<,<=,<>,Contains,Not contains,Reports Under,Member Of,AND,OR,(,)" The "field" attribute is not required if "op" is any of "Reports Under,Member Of,AND,OR,(,)" The "value" attribute is not required if "op" is any of "AND,OR,(,)" Note that if your rules contain any grouping or mixed logic then you will not be able to manage the rule via the browser. Example: <rules><rule op='Member of' value='sales department' /><rule op='AND' /><rule op='Contains' field='Department'value='Sales' /></rules> Parameters: -name <audience name> -rules <rules xml> | -rulesfile <xml file containing the rules> [-ssp <SSP name>] [-clear (clear existing rules)] [-compile] [-groupexisting (wraps any existing rules in parantheses)] [-appendop <and (default) | or> (operator used to append to existing rules)] |
The following table summarizes the command and its various parameters:
Command Name | Availability | Build Date |
---|---|---|
gl-addaudiencerule | MOSS 2007 | 8/6/2008 |
Parameter Name | Short Form | Required | Description | Example Usage |
---|---|---|---|---|
name | n | Yes | This is the name of the audience for which to apply the rules. | -name "IT Department"
-n "IT Department" |
ssp | No | The name of the SSP that the audience is associated with. If not specified then the default SSP is used. | -ssp SSP1 | |
rules | r | Yes – unless rulesfile provided | The XML rules that are to be created. Use tick marks instead of quotes. Using this parameter, as opposed to the rulesfile parameter, is convenient when using a batch script in which you’d like to pass variables into the XML. | -rules "<rules><rule op='(‘ /><rule field=’Department’ op=’=’ value=’IT’ /><rule op=’or’ /><rule op=’reports under’ value=’domain\glapointe’ /><rule op=’)’ /><rule op=’and’ /><rule field=’IsContractor’ op=’=’ value=’false’ /></rules>" |
rulesfile | rf | Yes – unless rules provided | Specifies the path to an XML file containing the rules to be created. The file extension does not matter. Using this parameter, as opposed to the rules parameter, is convenient when you’d like to save your rules for later reference or recreation in other environments as well as easy modification. | -rulesfile c:\Audiences\ITDepartment.rules |
clear | cl | No | If provided then any existing rules will be removed from the audience. | -clear
-cl |
compile | co | No | If provided then the audience will be compiled after adding the rules. | -compile
-co |
groupexisting | group | No | If provided then any existing rules will be grouped within parentheses. | -groupexisting
-group |
appendop | op | No | Specifies how the passed in rules will be appended to any existing rules. Valid values are "and" or "or". The default, if omitted, is "and". | -appendop or
-op or |
The following is an example of how to add rules to an audience named "IT Department":
stsadm -o gl-addaudiencerule -name "IT Department" -rules "<rules><rule op='(‘ /><rule field=’Department’ op=’=’ value=’IT’ /><rule op=’or’ /><rule op=’reports under’ value=’domain\glapointe’ /><rule op=’)’ /><rule op=’and’ /><rule field=’IsContractor’ op=’=’ value=’false’ /></rules>" -clear -compile
One thought on “Assigning Rules to Audiences via STSADM”
There is a typo in the example (gl-addaudiencerule(s)
Comments are closed.