SharePoint Automation Gary Lapointe – Founding Partner, Aptillon, Inc.

24Apr/092

Importing Audiences using STSADM

I recently posted about exporting audiences using my gl-exportaudiences STSADM command.  Of course an export wouldn’t be of much use if you didn’t also have an import so I give you gl-importaudiences.

Developing this was really easy as I already had code that created an audience and its associated rules.  All I had to do was read in the source XML file, do a little refactoring of the audience creation code and then call the rules creation code.  One cool thing I added was the ability to output a mapping file which provides the search and replace XML necessary to use my gl-replacefieldvalues command so that you can replace the old GUIDs used in audience targeting with the new GUID of the new audience.  The code can be seen below:

   1: #if MOSS
   2: using System;
   3: using System.Collections;
   4: using System.Collections.Specialized;
   5: using System.Collections.Generic;
   6: using System.IO;
   7: using System.Text;
   8: using System.Xml;
   9: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
  10: using Lapointe.SharePoint.STSADM.Commands.SPValidators;
  11: using Microsoft.Office.Server;
  12: using Microsoft.Office.Server.Audience;
  13:  
  14: namespace Lapointe.SharePoint.STSADM.Commands.Audiences
  15: {
  16:     public class ImportAudiences : SPOperation
  17:     {
  18:         /// <summary>
  19:         /// Initializes a new instance of the <see cref="ImportAudiences"/> class.
  20:         /// </summary>
  21:         public ImportAudiences()
  22:         {
  23:             SPParamCollection parameters = new SPParamCollection();
  24:             parameters.Add(new SPParam("ssp", "ssp", false, null, new SPNonEmptyValidator()));
  25:             parameters.Add(new SPParam("deleteexisting", "delete"));
  26:             parameters.Add(new SPParam("inputfile", "input", false, null, new SPFileExistsValidator()));
  27:             parameters.Add(new SPParam("compile", "c"));
  28:             parameters.Add(new SPParam("mapfile", "map", false, null, new SPDirectoryExistsAndValidFileNameValidator()));
  29:  
  30:             StringBuilder sb = new StringBuilder();
  31:             sb.Append("\r\n\r\nImports all audiences given the provided input file.\r\n\r\nParameters:");
  32:             sb.Append("\r\n\t-inputfile <file to input results from>");
  33:             sb.Append("\r\n\t[-deleteexisting <delete existing audiences>]");
  34:             sb.Append("\r\n\t[-ssp <SSP name>]");
  35:             sb.Append("\r\n\t[-compile]");
  36:             sb.Append("\r\n\t[-mapfile <generate a map file to use for search and replace of Audience IDs>]");
  37:             Init(parameters, sb.ToString());
  38:         }
  39:  
  40:         /// <summary>
  41:         /// Gets the help message.
  42:         /// </summary>
  43:         /// <param name="command">The command.</param>
  44:         /// <returns></returns>
  45:         public override string GetHelpMessage(string command)
  46:         {
  47:             return HelpMessage;
  48:         }
  49:  
  50:         /// <summary>
  51:         /// Executes the specified command.
  52:         /// </summary>
  53:         /// <param name="command">The command.</param>
  54:         /// <param name="keyValues">The key values.</param>
  55:         /// <param name="output">The output.</param>
  56:         /// <returns></returns>
  57:         public override int Execute(string command, StringDictionary keyValues, out string output)
  58:         {
  59:             output = string.Empty;
  60:  
  61:             string inputFile = Params["inputfile"].Value;
  62:             bool deleteExisting = Params["deleteexisting"].UserTypedIn;
  63:             bool compile = Params["compile"].UserTypedIn;
  64:             string mapFile = default(string);
  65:             if (Params["mapfile"].UserTypedIn)
  66:                 mapFile = Params["mapfile"].Value;
  67:  
  68:             string xml = File.ReadAllText(inputFile);
  69:  
  70:             Import(xml, Params["ssp"].Value, deleteExisting, compile, mapFile);
  71:  
  72:             return OUTPUT_SUCCESS;
  73:         }
  74:  
  75:         /// <summary>
  76:         /// Imports the specified XML.
  77:         /// </summary>
  78:         /// <param name="xml">The XML.</param>
  79:         /// <param name="sspName">Name of the SSP.</param>
  80:         /// <param name="deleteExisting">if set to <c>true</c> [delete existing].</param>
  81:         /// <param name="compile">if set to <c>true</c> [compile].</param>
  82:         /// <param name="mapFile">The map file.</param>
  83:         private void Import(string xml, string sspName, bool deleteExisting, bool compile, string mapFile)
  84:         {
  85:             ServerContext context;
  86:             if (string.IsNullOrEmpty(sspName))
  87:                 context = ServerContext.Default;
  88:             else
  89:                 context = ServerContext.GetContext(sspName);
  90:  
  91:             AudienceManager manager = new AudienceManager(context);
  92:  
  93:             XmlDocument audiencesDoc = new XmlDocument();
  94:             audiencesDoc.LoadXml(xml);
  95:  
  96:             XmlNodeList audienceElements = audiencesDoc.SelectNodes("//Audience");
  97:             if (audienceElements == null)
  98:                 throw new ArgumentException("The input file does not contain any audience elements.");
  99:  
 100:             StringBuilder sb = new StringBuilder();
 101:             XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
 102:             xmlWriter.Formatting = Formatting.Indented;
 103:             xmlWriter.WriteStartElement("Replacements");
 104:  
 105:             Dictionary<Guid, string> ids = new Dictionary<Guid, string>();
 106:             if (deleteExisting)
 107:             {
 108:                 Log("Progrss: Deleting existing audiences.");
 109:                 foreach (Audience au in manager.Audiences)
 110:                     ids.Add(au.AudienceID, au.AudienceName);
 111:  
 112:                 foreach (Guid id in ids.Keys)
 113:                 {
 114:                     if (id == Guid.Empty)
 115:                         continue;
 116:  
 117:                     string name = manager.Audiences[id].AudienceName;
 118:                     manager.Audiences.Remove(id);
 119:                 }
 120:             }
 121:  
 122:             foreach (XmlElement audienceElement in audienceElements)
 123:             {
 124:                 string audienceName = audienceElement.GetAttribute("AudienceName");
 125:                 string audienceDesc = audienceElement.GetAttribute("AudienceDescription");
 126:  
 127:                 Audience audience;
 128:                 bool updatedAudience = false;
 129:                 if (manager.Audiences.AudienceExist(audienceName))
 130:                 {
 131:                     Log("Progress: Updating audience {0}.", audienceName);
 132:                     audience = manager.Audiences[audienceName];
 133:                     audience.AudienceDescription = audienceDesc ?? "";
 134:                     updatedAudience = true;
 135:                 }
 136:                 else
 137:                 {
 138:                     // IMPORTANT: the create method does not do a null check but the methods that load the resultant collection assume not null.
 139:                     Log("Progress: Creating audience {0}.", audienceName);
 140:                     audience = manager.Audiences.Create(audienceName, audienceDesc ?? "");
 141:                 }
 142:  
 143:                 audience.GroupOperation = (AudienceGroupOperation)Enum.Parse(typeof (AudienceGroupOperation),
 144:                                                      audienceElement.GetAttribute("GroupOperation"));
 145:  
 146:                 audience.OwnerAccountName = audienceElement.GetAttribute("OwnerAccountName");
 147:  
 148:                 audience.Commit();
 149:  
 150:                 if (updatedAudience && audience.AudienceID != Guid.Empty)
 151:                 {
 152:                     // We've updated an existing audience.
 153:                     xmlWriter.WriteStartElement("Replacement");
 154:                     xmlWriter.WriteElementString("SearchString", (new Guid(audienceElement.GetAttribute("AudienceID")).ToString().ToUpper()));
 155:                     xmlWriter.WriteElementString("ReplaceString", string.Format("(?i:{0})", audience.AudienceID.ToString().ToUpper()));
 156:                     xmlWriter.WriteEndElement(); // Replacement
 157:                 }
 158:                 else if (!updatedAudience && audience.AudienceID != Guid.Empty && ids.ContainsValue(audience.AudienceName))
 159:                 {
 160:                     // We've added a new audience which we just previously deleted.
 161:                     xmlWriter.WriteStartElement("Replacement");
 162:                     foreach (Guid id in ids.Keys)
 163:                     {
 164:                         if (ids[id] == audience.AudienceName)
 165:                         {
 166:                             xmlWriter.WriteElementString("SearchString", id.ToString().ToUpper());
 167:                             break;
 168:                         }
 169:                     }
 170:                     xmlWriter.WriteElementString("ReplaceString", string.Format("(?i:{0})", audience.AudienceID.ToString().ToUpper()));
 171:                     xmlWriter.WriteEndElement(); // Replacement
 172:                 }
 173:  
 174:                 XmlElement rulesElement = (XmlElement)audienceElement.SelectSingleNode("rules");
 175:                 if (rulesElement == null || rulesElement.ChildNodes.Count == 0)
 176:                 {
 177:                     audience.AudienceRules = new ArrayList();
 178:                     audience.Commit();
 179:                     continue;
 180:                 }
 181:  
 182:  
 183:                 string rules = rulesElement.OuterXml;
 184:                 Log("Progress: Adding rules to audience {0}.", audienceName);
 185:                 AddAudienceRule.AddRules(sspName, audienceName, rules, true, compile, false, AddAudienceRule.AppendOp.AND);
 186:             }
 187:  
 188:             xmlWriter.WriteEndElement(); // Replacements
 189:  
 190:             if (!string.IsNullOrEmpty(mapFile))
 191:             {
 192:                 xmlWriter.Flush();
 193:                 File.WriteAllText(mapFile, sb.ToString());
 194:             }
 195:         }
 196:  
 197:     }
 198: }
 199: #endif

The help for the command is shown below:

C:\>stsadm -help gl-importaudiences

stsadm -o gl-importaudiences


Imports all audiences given the provided input file.

Parameters:
        -inputfile <file to input results from>
        [-deleteexisting <delete existing audiences>]
        [-ssp <SSP name>]
        [-compile]
        [-mapfile <generate a map file to use for search and replace of Audience IDs>]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-importaudiences MOSS 2007 Released: 4/24/2009

Parameter Name Short Form Required Description Example Usage
inputfile input Yes The path to the input file obtained via the gl-exportaudiences command. -inputfile c:\audiences.xml

-input c:\audiences.xml
deleteexisting delete No If specified then all existing audiences will be deleted prior to importing the audiences.  Note that the “All site users” audience will not be deleted or updated. -deleteexisting

-delete
ssp   No The name of the SSP to import the audiences into.  If omitted then the default SSP will be used. -ssp SSP1
compile c No If specified then compile the audience after creation/update. -compile

-c
mapfile map No If specified then an XML file will be generated providing the search and replace strings to use in order to update the GUIDs in your site collections. -mapfile c:\map.xml

-map c:\map.xml

The following is an example of how to import all audiences contained in the audiences.xml file:

stsadm -o gl-importaudiences -inputfile c:\audiences.xml -ssp SSP1 -mapfile c:\map.xml -compile

The following shows an example output of the map file after running the above command:

<Replacements>
  <Replacement>
    <SearchString>98E29BEF-8B1E-4113-BB15-6FAF1E6FB8D0</SearchString>
    <ReplaceString>(?i:1CA1F37E-A50A-4F84-BDCD-8C1279BADB3E)</ReplaceString>
  </Replacement>
</Replacements>
20Apr/092

Exporting Audiences using STSADM

I’d been wanting to build an export and import command for Audiences for quite some time but just haven’t gotten around to it.  I’m currently looking for a good sample command to build during a possible talk at the next Best Practices conference so I decided to give this one a whack considering that I already had a good chunk of the code written and just needed to repurpose it.  I don’t know if I’ll use this command for the presentation so if anyone has any good ideas of things they’d like to see please let me know and I’ll look into it.

One of the things I was hoping to achieve through this was the ability to do an import and preserve the ID of the Audience so that content targeting Audiences could be migrated between environments – unfortunately I quickly discovered that the ID is set via stored procedures during the creation of the Audience so there was no way for me to intercept the creation process and change the ID without going to the database directly which is something I was not willing to do.  I decided that the ability to migrate Audiences between environments, even without being able to set the ID, was still valuable so I went ahead with the creation of the commands.

To do the export I get an instance of the AudienceManager class and then loop through the collection of Audience objects using the Audiences property.  I then use an XmlTextWriter to write out all the properties of the Audience and then I loop through the AudienceRuleComponent objects which you can get via the AudienceRules property of the Audience object, once again writing out all the properties using the XmlTextWriter.  There’s really not much to it as you can see in the code below:

   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.SharePoint;
  13:  
  14: namespace Lapointe.SharePoint.STSADM.Commands.Audiences
  15: {
  16:     public class ExportAudiences : SPOperation
  17:     {
  18:         /// <summary>
  19:         /// Initializes a new instance of the <see cref="ExportAudiences"/> class.
  20:         /// </summary>
  21:         public ExportAudiences()
  22:         {
  23:             SPParamCollection parameters = new SPParamCollection();
  24:             parameters.Add(new SPParam("name", "n", false, null, new SPNonEmptyValidator()));
  25:             parameters.Add(new SPParam("ssp", "ssp", false, null, new SPNonEmptyValidator()));
  26:             parameters.Add(new SPParam("explicit", "ex"));
  27:             parameters.Add(new SPParam("outputfile", "output", false, null, new SPDirectoryExistsAndValidFileNameValidator()));
  28:  
  29:             StringBuilder sb = new StringBuilder();
  30:             sb.Append("\r\n\r\nExports all audiences or a specific audience if a name is provided.\r\n\r\nParameters:");
  31:             sb.Append("\r\n\t-outputfile <file to output results to>");
  32:             sb.Append("\r\n\t[-name <audience name>]");
  33:             sb.Append("\r\n\t[-ssp <SSP name>]");
  34:             sb.Append("\r\n\t[-explicit (shows field and value attributes for every rule)]");
  35:             Init(parameters, sb.ToString());
  36:         }
  37:  
  38:         /// <summary>
  39:         /// Gets the help message.
  40:         /// </summary>
  41:         /// <param name="command">The command.</param>
  42:         /// <returns></returns>
  43:         public override string GetHelpMessage(string command)
  44:         {
  45:             return HelpMessage;
  46:         }
  47:  
  48:         /// <summary>
  49:         /// Executes the specified command.
  50:         /// </summary>
  51:         /// <param name="command">The command.</param>
  52:         /// <param name="keyValues">The key values.</param>
  53:         /// <param name="output">The output.</param>
  54:         /// <returns></returns>
  55:         public override int Execute(string command, StringDictionary keyValues, out string output)
  56:         {
  57:             output = string.Empty;
  58:  
  59:             string outputFile = Params["outputfile"].Value;
  60:             string xml = Export(Params["ssp"].Value, Params["name"].Value, Params["explicit"].UserTypedIn);
  61:  
  62:             File.WriteAllText(outputFile, xml);
  63:  
  64:             return OUTPUT_SUCCESS;
  65:         }
  66:  
  67:         /// <summary>
  68:         /// Returns an XML structure containing all the audience details.
  69:         /// </summary>
  70:         /// <param name="sspName">Name of the SSP.</param>
  71:         /// <param name="audienceName">Name of the audience.</param>
  72:         /// <param name="includeAllAttributes">if set to <c>true</c> [include all attributes].</param>
  73:         /// <returns></returns>
  74:         private string Export(string sspName, string audienceName, bool includeAllAttributes)
  75:         {
  76:             ServerContext context;
  77:             if (string.IsNullOrEmpty(sspName))
  78:                 context = ServerContext.Default;
  79:             else
  80:                 context = ServerContext.GetContext(sspName);
  81:  
  82:             AudienceManager manager = new AudienceManager(context);
  83:  
  84:             if (!string.IsNullOrEmpty(audienceName) && !manager.Audiences.AudienceExist(audienceName))
  85:             {
  86:                 throw new SPException("Audience name does not exist");
  87:             }
  88:  
  89:             StringBuilder sb = new StringBuilder();
  90:             XmlTextWriter xmlWriter = new XmlTextWriter(new StringWriter(sb));
  91:             xmlWriter.Formatting = Formatting.Indented;
  92:  
  93:             xmlWriter.WriteStartElement("Audiences");
  94:  
  95:             if (!string.IsNullOrEmpty(audienceName))
  96:             {
  97:                 Audience audience = manager.Audiences[audienceName];
  98:                 ExportAudience(xmlWriter, audience, includeAllAttributes);
  99:             }
 100:             else
 101:             {
 102:                 foreach (Audience audience in manager.Audiences)
 103:                     ExportAudience(xmlWriter, audience, includeAllAttributes);
 104:             }
 105:  
 106:             xmlWriter.WriteEndElement(); // Audiences
 107:             xmlWriter.Flush();
 108:             return sb.ToString();
 109:         }
 110:  
 111:         /// <summary>
 112:         /// Exports the audience.
 113:         /// </summary>
 114:         /// <param name="xmlWriter">The XML writer.</param>
 115:         /// <param name="audience">The audience.</param>
 116:         /// <param name="includeAllAttributes">if set to <c>true</c> [include all attributes].</param>
 117:         private static void ExportAudience(XmlWriter xmlWriter, Audience audience, bool includeAllAttributes)
 118:         {
 119:             xmlWriter.WriteStartElement("Audience");
 120:             xmlWriter.WriteAttributeString("AudienceDescription", audience.AudienceDescription);
 121:             xmlWriter.WriteAttributeString("AudienceID", audience.AudienceID.ToString());
 122:             xmlWriter.WriteAttributeString("AudienceName", audience.AudienceName);
 123:             xmlWriter.WriteAttributeString("AudienceSite", audience.AudienceSite);
 124:             xmlWriter.WriteAttributeString("CreateTime", audience.CreateTime.ToString());
 125:             xmlWriter.WriteAttributeString("GroupOperation", audience.GroupOperation.ToString());
 126:             xmlWriter.WriteAttributeString("LastCompilation", audience.LastCompilation.ToString());
 127:             xmlWriter.WriteAttributeString("LastError", audience.LastError);
 128:             xmlWriter.WriteAttributeString("LastPropertyUpdate", audience.LastPropertyUpdate.ToString());
 129:             xmlWriter.WriteAttributeString("LastRuleUpdate", audience.LastRuleUpdate.ToString());
 130:             xmlWriter.WriteAttributeString("MemberShipCount", audience.MemberShipCount.ToString());
 131:             xmlWriter.WriteAttributeString("OwnerAccountName", audience.OwnerAccountName);
 132:  
 133:  
 134:             ArrayList audienceRules = audience.AudienceRules;
 135:             xmlWriter.WriteStartElement("rules");
 136:             if (audienceRules != null && audienceRules.Count > 0)
 137:             {
 138:                 foreach (AudienceRuleComponent rule in audienceRules)
 139:                 {
 140:                     xmlWriter.WriteStartElement("rule");
 141:                     if (includeAllAttributes)
 142:                     {
 143:                         xmlWriter.WriteAttributeString("field", rule.LeftContent);
 144:                         xmlWriter.WriteAttributeString("op", rule.Operator);
 145:                         xmlWriter.WriteAttributeString("value", rule.RightContent);
 146:                     }
 147:                     else
 148:                     {
 149:                         switch (rule.Operator.ToLowerInvariant())
 150:                         {
 151:                             case "=":
 152:                             case ">":
 153:                             case ">=":
 154:                             case "<":
 155:                             case "<=":
 156:                             case "contains":
 157:                             case "<>":
 158:                             case "not contains":
 159:                                 xmlWriter.WriteAttributeString("field", rule.LeftContent);
 160:                                 xmlWriter.WriteAttributeString("op", rule.Operator);
 161:                                 xmlWriter.WriteAttributeString("value", rule.RightContent);
 162:                                 break;
 163:                             case "reports under":
 164:                             case "member of":
 165:                                 xmlWriter.WriteAttributeString("op", rule.Operator);
 166:                                 xmlWriter.WriteAttributeString("value", rule.RightContent);
 167:                                 break;
 168:                             case "and":
 169:                             case "or":
 170:                             case "(":
 171:                             case ")":
 172:                                 xmlWriter.WriteAttributeString("op", rule.Operator);
 173:                                 break;
 174:                         }
 175:                     }
 176:                     xmlWriter.WriteEndElement(); // rule
 177:                 }
 178:             }
 179:             xmlWriter.WriteEndElement(); // rules
 180:             xmlWriter.WriteEndElement(); // Audience
 181:         }
 182:     }
 183: }
 184: #endif

The help for the command is shown below:

C:\>stsadm -help gl-exportaudiences

stsadm -o gl-exportaudiences


Exports all audiences or a specific audience if a name is provided.

Parameters:
        -outputfile <file to output results to>
        [-name <audience name>]
        [-ssp <SSP name>]
        [-explicit (shows field and value attributes for every rule)]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-exportaudiences MOSS 2007 Released: 4/17/2009

Parameter Name Short Form Required Description Example Usage
outputfile output Yes The path to the output file to save the exported audience details. -outputfile c:\audiences.xml

-output c:\audiences.xml
name n No The name of a specific audience if only one audience is to be exported. -name HR

-n HR
ssp   No The name of the SSP containing the audiences to export.  If omitted then the default SSP will be used. -ssp SSP1
explicit ex No If specified then all field and value properties for the rules will be populated (in many cases these properties are redundant and do not add any information and are not necessary for import). -explicit

The following is an example of how to export all audiences belonging to SSP1:

stsadm -o gl-exportaudiences -outputfile c:\audiences.xml -ssp SSP1

The following shows an example output of running the above command:

<Audiences>
  <Audience AudienceDescription="All users who can access the site" AudienceID="00000000-0000-0000-0000-000000000000" AudienceName="All site users" AudienceSite="http://sspadmin/ssp/admin" CreateTime="1/1/0001 12:00:00 AM" GroupOperation="AUDIENCE_NOGROUP_OPERATION" LastCompilation="1/1/0001 12:00:00 AM" LastError="" LastPropertyUpdate="1/1/0001 12:00:00 AM" LastRuleUpdate="1/1/0001 12:00:00 AM" MemberShipCount="0" OwnerAccountName="">
    <rules />
  </Audience>
  <Audience AudienceDescription="" AudienceID="a2e62d72-fa52-4772-a5d0-9b950ec7d220" AudienceName="HR" AudienceSite="http://sspadmin/ssp/admin" CreateTime="4/20/2009 8:23:48 PM" GroupOperation="AUDIENCE_OR_OPERATION" LastCompilation="4/20/2009 8:26:22 PM" LastError="" LastPropertyUpdate="4/20/2009 8:26:07 PM" LastRuleUpdate="4/20/2009 8:26:07 PM" MemberShipCount="6" OwnerAccountName="spdev\spadmin">
    <rules>
      <rule op="Member of" value="CN=Human Resources Dept,OU=Users,DC=CompanyName,DC=com" />
      <rule op="OR" />
      <rule field="Department" op="=" value="Human Resources" />
    </rules>
  </Audience>
</Audiences>

16Aug/083

Enabling Audience Targeting on a List

I thought I was done documenting my audience related STSADM commands and then I realized that I had completely forgotten one.  The first audience related command I had created was to enable audience targeting on a list - I called it gl-listaudiencetargeting.  I needed this because my current project had tons of lists that that needed audience targeting turned on.  Eventually these lists were added via a Feature but initially I did it via the command line with STSADM so that we could get a demo put together.  The nice thing is that adding the code to the Feature became a no-brainer because the code was already tested so it became simple copy and paste.

So, how do you programmatically enable audience targeting?  When I first started on this I thought it would be easy as setting a property on the list because enabling it via the browser is as simple as setting a checkbox but turns out it's not quite that simple.  The way audience targeting works is that a "special" field is added to the list.  The field schema looks like this:

<Field ID="61cbb965-1e04-4273-b658-eedaa662f48d" Type="TargetTo" Name="TargetTo" DisplayName="Target Audience" Required="FALSE" />

Once we have this schema we can add the field to the list using the AddFieldAsXml method (member of the SPFieldCollection class which can be obtained via the lists Fields property):

   1: /// <summary>
   2: /// Sets the whether audience targeting is enabled or not.
   3: /// </summary>
   4: /// <param name="url">The URL.</param>
   5: /// <param name="enabled">if set to <c>true</c> [enabled].</param>
   6: public static void SetTargeting(string url, bool enabled)
   7: {
   8:     using (SPSite site = new SPSite(url))
   9:     using (SPWeb web = site.OpenWeb())
  10:     {
  11:         SPList list = Utilities.GetListFromViewUrl(web, url);
  12:  
  13:         if (list == null)
  14:             throw new SPException("List was not found.");
  15:  
  16:         SPField targetingField = GetTargetingField(list);
  17:         if (enabled && (targetingField == null))
  18:         {
  19:             string createFieldAsXml = CreateFieldAsXml();
  20:             list.Fields.AddFieldAsXml(createFieldAsXml);
  21:             list.Update();
  22:         }
  23:         else if (!enabled && (targetingField != null))
  24:         {
  25:             list.Fields.Delete(targetingField.InternalName);
  26:             list.Update();
  27:         }
  28:  
  29:     }
  30: }
  31:  
  32: /// <summary>
  33: /// Gets the targeting field.
  34: /// </summary>
  35: /// <param name="list">The list.</param>
  36: /// <returns></returns>
  37: private static SPField GetTargetingField(SPList list)
  38: {
  39:     SPField field = null;
  40:     try
  41:     {
  42:         field = list.Fields[new Guid("61cbb965-1e04-4273-b658-eedaa662f48d")];
  43:     }
  44:     catch (ArgumentException)
  45:     {
  46:     }
  47:     return field;
  48: }
  49:  
  50: /// <summary>
  51: /// Gets the field as XML.
  52: /// </summary>
  53: /// <returns></returns>
  54: private static string CreateFieldAsXml()
  55: {
  56:     XmlElement element = new XmlDocument().CreateElement("Field");
  57:     element.SetAttribute("ID", "61cbb965-1e04-4273-b658-eedaa662f48d");
  58:     element.SetAttribute("Type", "TargetTo");
  59:     element.SetAttribute("Name", "TargetTo");
  60:     element.SetAttribute("DisplayName", "Target Audiences");
  61:     element.SetAttribute("Required", "FALSE");
  62:     return element.OuterXml;
  63: }

The help for the command is shown below:

C:\>stsadm -help gl-listaudiencetargeting

stsadm -o gl-listaudiencetargeting


Enabling audience targeting will create a targeting column for the list. Web parts, such as the Content Query Web Part, can use this data to filter list contents based on the user's context.

Parameters:
        -url <list view url>
        -enabled <true|false>

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-listaudiencetargeting MOSS 2007 Released: 8/6/2008

Parameter Name Short Form Required Description Example Usage
url url Yes The URL to the list that you wish to enable audience targeting on.  This can be the URL to the root folder or to a specific list view. -url http://portal/lists/announcements

or

-url http://portal/lists/announcements/forms/allitems.aspx
enabled e Yes "true" to enable audience targeting, "false" to disable audience targeting.  Note that disabling audience targeting deletes the "Target Audience" field and therefore any data that may have been assigned to the field. -enabled true

The following is an example of how to enable audience targeting for an announcements list:

stsadm -o gl-listaudiencetargeting -url http://portal/lists/announcements -enabled true

14Aug/081

Setting the Audience Compilation Schedule via STSADM

In an effort to wrap up my audience related STSADM commands I created a command that allows me to set the audience compilation schedule via STSADM.  I had to do some disassembling to figure out how to do this and it turned out that the code was virtually identical to what I had done for the gl-setuserprofileimportschedule command.  So it turned out that I was able to create this command by simply coping the code from my other command and then just tweaking a couple lines to load up different class types.  I named the command gl-setaudiencecompilationschedule.  The downside of this code (and the code it's based off of) is that I had to use reflection to get it done as all the classes are marked internally (no idea why).  If anyone knows of a way to do this without all the reflect I'm all ears.

Here's the code - it's ugly, but it works:

   1: #if MOSS
   2: using System;
   3: using System.Collections.Specialized;
   4: using System.Reflection;
   5: using System.Text;
   6: using System.Threading;
   7: using Lapointe.SharePoint.STSADM.Commands.SPValidators;
   8: using Microsoft.Office.Server;
   9: using Microsoft.Office.Server.UserProfiles;
  10: using Microsoft.SharePoint;
  11: using Microsoft.SharePoint.Administration;
  12: using Microsoft.SharePoint.StsAdmin;
  13: using PropertyInfo=System.Reflection.PropertyInfo;
  14: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
  15:  
  16: namespace Lapointe.SharePoint.STSADM.Commands.TimerJob
  17: {
  18:     public class SetAudienceCompilationSchedule : SPOperation
  19:     {
  20:         private enum OccurrenceType
  21:         {
  22:             daily,
  23:             weekly,
  24:             monthly
  25:         }
  26:  
  27:         /// <summary>
  28:         /// Initializes a new instance of the <see cref="SetAudienceCompilationSchedule"/> class.
  29:         /// </summary>
  30:         public SetAudienceCompilationSchedule()
  31:         {
  32:             SPParamCollection parameters = new SPParamCollection();
  33:             parameters.Add(new SPParam("sspname", "ssp", false, null, new SPNonEmptyValidator(), "Please specify the SSP name."));
  34:             parameters.Add(new SPParam("occurrence", "oc", true, null, new SPRegexValidator("^daily$|^weekly$|^monthly$")));
  35:             parameters.Add(new SPParam("hour", "hour", true, null, new SPIntRangeValidator(0, 23)));
  36:             parameters.Add(new SPParam("day", "day", false, null, new SPIntRangeValidator(1, 31)));
  37:             string regex = "^" + string.Join("$|^", Enum.GetNames(typeof (DayOfWeek))) + "$";
  38:             parameters.Add(new SPParam("dayofweek", "dayofweek", false, null, new SPRegexValidator(regex.ToLowerInvariant() + "|" + regex)));
  39:             parameters.Add(new SPParam("enabled", "enabled", false, "true", new SPTrueFalseValidator()));
  40:             parameters.Add(new SPParam("runjob", "run"));
  41:            
  42:             StringBuilder sb = new StringBuilder();
  43:             sb.Append("\r\n\r\nSets the audience compilation schedule.\r\n\r\nParameters:");
  44:             sb.Append("\r\n\t[-sspname <SSP name>]");
  45:             sb.Append("\r\n\t-occurrence <daily|weekly|monthly>");
  46:             sb.Append("\r\n\t-hour <hour to run (0-23)>");
  47:             sb.Append("\r\n\t[-day <the day to run if monthly is specified>]");
  48:             sb.AppendFormat("\r\n\t[-dayofweek <the day of week to run if weekly is specified ({0})>]", string.Join("|", Enum.GetNames(typeof(DayOfWeek))).ToLowerInvariant());
  49:             sb.Append("\r\n\t[-enabled <true|false> (default is true)]");
  50:             sb.Append("\r\n\t[-runjob]");
  51:             Init(parameters, sb.ToString());
  52:         }
  53:  
  54:         #region ISPStsadmCommand Members
  55:  
  56:         /// <summary>
  57:         /// Gets the help message.
  58:         /// </summary>
  59:         /// <param name="command">The command.</param>
  60:         /// <returns></returns>
  61:         public override string GetHelpMessage(string command)
  62:         {
  63:             return HelpMessage;
  64:         }
  65:  
  66:         /// <summary>
  67:         /// Runs the specified command.
  68:         /// </summary>
  69:         /// <param name="command">The command.</param>
  70:         /// <param name="keyValues">The key values.</param>
  71:         /// <param name="output">The output.</param>
  72:         /// <returns></returns>
  73:         public override int Execute(string command, StringDictionary keyValues, out string output)
  74:         {
  75:             output = string.Empty;
  76:  
  77:             
  78:  
  79:             #region Check Arguments
  80:  
  81:             OccurrenceType occurrence = (OccurrenceType)Enum.Parse(typeof(OccurrenceType), Params["occurrence"].Value, true);
  82:             if (occurrence == OccurrenceType.monthly && !Params["day"].UserTypedIn)
  83:             {
  84:                 output = "Please specify the day to run the import.";
  85:                 output += GetHelpMessage(command);
  86:                 return (int)ErrorCodes.SyntaxError;
  87:             }
  88:             if (occurrence == OccurrenceType.weekly && !Params["dayofweek"].UserTypedIn)
  89:             {
  90:                 output = "Please specify the day of week to run the import.";
  91:                 output += GetHelpMessage(command);
  92:                 return (int)ErrorCodes.SyntaxError;
  93:             }
  94:  
  95:             #endregion
  96:  
  97:             string day = Params["day"].Value;
  98:             string dayofweek = Params["dayofweek"].Value;
  99:             string sspname = Params["sspname"].Value;
 100:             int hour = int.Parse(Params["hour"].Value);
 101:             bool enabled = bool.Parse(Params["enabled"].Value);
 102:             bool runJob = Params["runjob"].UserTypedIn;
 103:             if (!enabled && runJob)
 104:                 throw new SPSyntaxException("The runjob parameter cannot be specified when enabled is set to false.");
 105:  
 106:             ServerContext current;
 107:             if (Params["sspname"].UserTypedIn)
 108:                 current = ServerContext.GetContext(sspname);
 109:             else
 110:                 current = ServerContext.Default;
 111:  
 112:             // What follows is a whole lot of reflection which is required in order to get the SPScheduledJob object.
 113:             // Problem is that the only way to get the correct instance of this object is to use several internal
 114:             // classes, methods, and properties - why on earth these were not made public is absolutely beyond me!
 115:  
 116:             // The bulk of the reflection is recreating the following which was taken from 
 117:             // Microsoft.SharePoint.Portal.UserProfiles.AdminUI.Sched.InitializeComponent().
 118:             // Once we have the job objects we can start setting properties.
 119:             /*
 120:             private void InitializeComponent()
 121:             {
 122:                 ServerContext current = ServerContext.Current;
 123:                 UserProfileApplication userProfileApplication = current.UserProfileApplication;
 124:                 try
 125:                 {
 126:                     using (PortalApplication.BeginSecurityContext())
 127:                     {
 128:                         JobSchedulerSharedApplicationCollection applications = new JobSchedulerSharedApplicationCollection(SPFarm.Local.Services.GetValue<JobSchedulerService>(string.Empty));
 129:                         JobSchedulerSharedApplication sharedApplication = (JobSchedulerSharedApplication) applications[current.SharedResourceProvider];
 130:                         ScheduledJobCollection jobs = new ScheduledJobCollection(sharedApplication);
 131:                         this.AudienceCompileScheduler.Job = jobs[userProfileApplication.AudienceCompilationJobId];
 132:                     }
 133:                 }
 134:                 catch (Exception)
 135:                 {
 136:                     throw;
 137:                 }
 138:             }
 139:             */
 140:  
 141:             // UserProfileApplication userProfileApplication = current.UserProfileApplication;
 142:             object userProfileApplication = Utilities.GetPropertyValue(current, "UserProfileApplication");
 143:  
 144:             // The SSP is locked down so we need to use reflection to get at it.
 145:             object sharedResourceProvider = Utilities.GetSharedResourceProvider(current);
 146:  
 147:             // JobSchedulerService jobSchedulerService = SPFarm.Local.Services.GetValue(typeof(JobSchedulerService));
 148:             Type jobSchedulerServiceType = Type.GetType("Microsoft.Office.Server.Administration.JobSchedulerService, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
 149:  
 150:  
 151:             MethodInfo getValue =
 152:                 SPFarm.Local.Services.GetType().GetMethod("GetValue",
 153:                                                           BindingFlags.NonPublic | BindingFlags.Public |
 154:                                                           BindingFlags.Instance | BindingFlags.InvokeMethod, null, new Type[] {typeof(Type), typeof(string)}, null);
 155:  
 156:             object jobSchedulerService = getValue.Invoke(SPFarm.Local.Services,
 157:                                                           new object[]
 158:                                                               {
 159:                                                                   jobSchedulerServiceType, string.Empty
 160:                                                               });
 161:  
 162:  
 163:             // JobSchedulerSharedApplicationCollection application = new JobSchedulerSharedApplicationCollection(jobSchedulerServiceType);
 164:             Type jobSchedulerSharedApplicationCollectionType = Type.GetType("Microsoft.Office.Server.Administration.JobSchedulerSharedApplicationCollection, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
 165:  
 166:             ConstructorInfo jobSchedulerSharedApplicationCollectionConstructor =
 167:                 jobSchedulerSharedApplicationCollectionType.GetConstructor(
 168:                     BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
 169:                     null,
 170:                     new Type[] {jobSchedulerService.GetType()}, null);
 171:             object applications = jobSchedulerSharedApplicationCollectionConstructor.Invoke(new object[] { jobSchedulerService });
 172:  
 173:             // JobSchedulerSharedApplication jobSchedulerSharedApplication = applications[sharedResourceProvider];
 174:             PropertyInfo itemProp = applications.GetType().GetProperty("Item",
 175:                                                                      BindingFlags.NonPublic |
 176:                                                                      BindingFlags.Instance |
 177:                                                                      BindingFlags.InvokeMethod |
 178:                                                                      BindingFlags.GetProperty |
 179:                                                                      BindingFlags.Public);
 180:             object jobSchedulerSharedApplication = itemProp.GetValue(applications, new object[] { sharedResourceProvider });
 181:  
 182:  
 183:             //ScheduledJobCollection scheduledJobCollection = new ScheduledJobCollection(sharedApplication);
 184:             Type scheduledJobCollectionType = Type.GetType("Microsoft.Office.Server.Administration.ScheduledJobCollection, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
 185:             ConstructorInfo scheduledJobCollectionConstructor =
 186:                 scheduledJobCollectionType.GetConstructor(
 187:                     BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public,
 188:                     null,
 189:                     new Type[] {jobSchedulerSharedApplication.GetType()}, null);
 190:             object scheduledJobCollection = scheduledJobCollectionConstructor.Invoke(new object[] { jobSchedulerSharedApplication });
 191:  
 192:  
 193:             // userProfileApplication.AudienceCompilationJobId
 194:             Guid audienceCompilationJobId = (Guid)Utilities.GetPropertyValue(userProfileApplication, "AudienceCompilationJobId");
 195:  
 196:  
 197:  
 198:             // ScheduledJob compilationJob = scheduledJobCollection[audienceCompilationJobId];
 199:             itemProp = scheduledJobCollection.GetType().GetProperty("Item",
 200:                                                                     BindingFlags.NonPublic |
 201:                                                                     BindingFlags.Instance |
 202:                                                                     BindingFlags.InvokeMethod |
 203:                                                                     BindingFlags.GetProperty |
 204:                                                                     BindingFlags.Public);
 205:             object compilationJob = itemProp.GetValue(scheduledJobCollection, new object[] { audienceCompilationJobId });
 206:  
 207:  
 208:             PropertyInfo scheduleProp = compilationJob.GetType().GetProperty("Schedule",
 209:                                                                             BindingFlags.FlattenHierarchy |
 210:                                                                             BindingFlags.NonPublic |
 211:                                                                             BindingFlags.Instance |
 212:                                                                             BindingFlags.InvokeMethod |
 213:                                                                             BindingFlags.GetProperty |
 214:                                                                             BindingFlags.Public);
 215:  
 216:             MethodInfo update =
 217:                 compilationJob.GetType().GetMethod("Update",
 218:                                                   BindingFlags.NonPublic | 
 219:                                                   BindingFlags.Public |
 220:                                                   BindingFlags.Instance | 
 221:                                                   BindingFlags.InvokeMethod |
 222:                                                   BindingFlags.FlattenHierarchy, 
 223:                                                   null,
 224:                                                   new Type[] {typeof (bool)}, null);
 225:             
 226:             // Woohoo!!! We are finally at a point where we can actually set the schedule - what a pain the @$$ that was!!!
 227:             SPSchedule schedule;
 228:             
 229:             if (occurrence == OccurrenceType.daily)
 230:             {
 231:                 schedule = SetUserProfileImportSchedule.ScheduledJobHelper.GetScheduleDaily(hour);
 232:             }
 233:             else if (occurrence == OccurrenceType.weekly)
 234:             {
 235:                 schedule = SetUserProfileImportSchedule.ScheduledJobHelper.GetScheduleWeekly((DayOfWeek)Enum.Parse(typeof(DayOfWeek), dayofweek, true), hour);
 236:             }
 237:             else if (occurrence == OccurrenceType.monthly)
 238:             {
 239:                 schedule = SetUserProfileImportSchedule.ScheduledJobHelper.GetScheduleMonthly(int.Parse(day), hour);
 240:             }
 241:             else
 242:                 throw new Exception("Unknown occurance type.");
 243:  
 244:             Type scheduledJobType = Type.GetType("Microsoft.Office.Server.Administration.ScheduledJob, Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
 245:  
 246:  
 247:             // fullImportJob.Schedule = schedule;
 248:             scheduleProp.SetValue(compilationJob, schedule, null);
 249:  
 250:             // fullImportJob.Enabled = enabled;
 251:             Utilities.SetPropertyValue(compilationJob, scheduledJobType, "Disabled", !enabled);
 252:  
 253:             // fullImportJob.Update(true);
 254:             update.Invoke(compilationJob, new object[] { true });
 255:  
 256:             if (runJob)
 257:             {
 258:                 // fullImportJob.Execute();
 259:                 Utilities.ExecuteMethod(compilationJob, "Execute", new Type[] { }, new object[] { });
 260:             }
 261:  
 262:             if (runJob)
 263:             {
 264:                 // We want to wait until the import is finished before moving on in case we are being run in a batch that requires this to complete before continueing.
 265:                 UserProfileConfigManager manager = new UserProfileConfigManager(current);
 266:                 while (manager.IsImportInProgress())
 267:                     Thread.Sleep(500);
 268:             }
 269:  
 270:  
 271:             return OUTPUT_SUCCESS;
 272:         }
 273:  
 274:         #endregion
 275:  
 276:     }
 277: }
 278: #endif

The help for the command is shown below:

C:\>stsadm -help gl-setaudiencecompilationschedule

stsadm -o gl-setaudiencecompilationschedule


Sets the audience compilation schedule.

Parameters:
        [-sspname <SSP name>]
        -occurrence <daily|weekly|monthly>
        -hour <hour to run (0-23)>
        [-day <the day to run if monthly is specified>]
        [-dayofweek <the day of week to run if weekly is specified (sunday|monday|tuesday|wednesday|thursday|friday|saturday)>]
        [-enabled <true|false> (default is true)]
        [-runjob]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-setaudiencecompilationschedule MOSS 2007 Release: 8/14/2008

Parameter Name Short Form Required Description Example Usage
sspname ssp No The name of the SSP that the audiences to compile are associated with.  If omitted the default SSP will be used. -sspname SSP1

-ssp SSP1
occurrence oc Yes Specifies how frequently the compilation should occur.  Valid values are "daily", "weekly", and "monthly". -occurrence daily

-oc monthly
hour   Yes The hour in which to run the compilation.  This should be an integer between 0 and 23 (where 0 is 12:00am and 23 is 11:00pm). -hour 22
day   No, unless occurrence is monthly The day of the month to run the compilation job.  Valid values are between 1 and 31. -day 1
dayofweek   No, unless occurrence is weekly The day of the week to run the compilation job.  Valid values are "sunday", "monday", "tuesday", "wednesday", "thursday", and "saturday". -dayofweek saturday
enabled   No "true" to enable the compilation schedule, "false" to disable it.  If not specified then the compilation schedule will be enabled. -enabled true
runjob run No If specified then the compilation job will be immediately executed after setting the schedule. -runjob

-run

The following is an example of how to set the compilation schedule to run every Satruday at 10:00pm:

stsadm -o gl-setaudiencecompilationschedule -occurrence weekly -hour 22 -dayofweek saturday -enabled true -runjob

14Aug/080

Deleting an Audience via STSADM

Using the commands I've created so far you can now create audiences, add rules, and display those rules all via STSADM.  If you're like me and you do a lot of testing of this stuff before you push out the final version then you'll want a way to also easily delete audiences that you've created.  Of course you could easily do this via the browser but what if you've added complex rules to your audience using my gl-addaudiencerule command?  You'll find that you can't delete it from the browser.  To deal with this I've created an additional command called, simply enough, gl-deleteaudience.

One thing I added to the command was an option to delete only the rules and not the audience itself.  This allows you to essentially revert the audience back to a pure state so that you can manage it via the browser if you so desire.  The code to delete an audience is real simple - essentially one line: manager.Audiences.Remove(audience.AudienceID); - the rest is just error handling and clearing of the rules:

   1: /// <summary>
   2: /// Deletes the specified audience or all audience rules for the specified audience.
   3: /// </summary>
   4: /// <param name="sspName">Name of the SSP.</param>
   5: /// <param name="audienceName">Name of the audience.</param>
   6: /// <param name="deleteRulesOnly">if set to <c>true</c> [delete rules only].</param>
   7: private void Delete(string sspName, string audienceName, bool deleteRulesOnly)
   8: {
   9:     ServerContext context;
  10:     if (string.IsNullOrEmpty(sspName))
  11:         context = ServerContext.Default;
  12:     else
  13:         context = ServerContext.GetContext(sspName);
  14:  
  15:     AudienceManager manager = new AudienceManager(context);
  16:  
  17:     if (!manager.Audiences.AudienceExist(audienceName))
  18:     {
  19:         throw new SPException("Audience name does not exist");
  20:     }
  21:  
  22:     Audience audience = manager.Audiences[audienceName];
  23:  
  24:     if (audience.AudienceRules != null && deleteRulesOnly)
  25:     {
  26:         audience.AudienceRules = new ArrayList();
  27:         audience.GroupOperation = AudienceGroupOperation.AUDIENCE_OR_OPERATION;
  28:         audience.Commit();
  29:         return;
  30:     }
  31:     if (!deleteRulesOnly)
  32:         manager.Audiences.Remove(audience.AudienceID);
  33: }

The help for the command is shown below:

C:\>stsadm -help gl-deleteaudience

stsadm -o gl-deleteaudience


Deletes an audience.  To delete only the rules associated with the audience pass in the -rulesonly parameter.

Parameters:
        -name <audience name>
        [-ssp <SSP name>]
        [-rulesonly (delete only the rules)]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-deleteaudience MOSS 2007 8/6/2008

Parameter Name Short Form Required Description Example Usage
name n Yes The name of the audience to delete. -name "IT"

-n "IT"
ssp   No The name of the SSP that the audience is associated with.  If omitted the default SSP will be used. -ssp SSP1
-rulesonly ro No If specified then only the audience rules will be deleted and not the audience itself. -rulesonly

-ro

The following is an example of how to delete an audience:

stsadm -o gl-deleteaudience -name "IT"

The following is an example of how to delete only the rules associated with an audience

stsadm -o gl-deleteaudience -name "IT" -rulesonly

9Aug/080

Displaying Audience Rules via STSADM

Okay, so you've created your audience and used my gl-addaudiencerule command to add some complex rules to the audience.  Three months later you've forgotten what those rules are and need to see them again - unfortunately you can't do that via the browser, so what do you do?  Simple, you run gl-enumaudiencerules, another command I've created to help manage audiences via STSADM.

This command is really simple so I'll be brief - I simply loop through all the rules associated with an audience and display back an XML structure representing those rules.  The nice thing about this is that you can use it to help build your rules to assign using the gl-addaudiencerule command - you'd create all the rules you need via the browser, with no groupings, and then use this command to get the XML and then simply add your grouping elements into the XML - this way you're sure the field names are correct.

Here's a snippet of the code:

   1:  
   2: /// <summary>
   3: /// Returns an XML structure containing all the rules associated with the audience.
   4: /// </summary>
   5: /// <param name="sspName">Name of the SSP.</param>
   6: /// <param name="audienceName">Name of the audience.</param>
   7: /// <param name="includeAllAttributes">if set to <c>true</c> [include all attributes].</param>
   8: /// <returns></returns>
   9: private string EnumRules(string sspName, string audienceName, bool includeAllAttributes)
  10: {
  11:     ServerContext context;
  12:     if (string.IsNullOrEmpty(sspName))
  13:         context = ServerContext.Default;
  14:     else
  15:         context = ServerContext.GetContext(sspName);
  16:  
  17:     AudienceManager manager = new AudienceManager(context);
  18:  
  19:     if (!manager.Audiences.AudienceExist(audienceName))
  20:     {
  21:         throw new SPException("Audience name does not exist");
  22:     }
  23:  
  24:     Audience audience = manager.Audiences[audienceName];
  25:  
  26:     ArrayList audienceRules = audience.AudienceRules;
  27:  
  28:     if (audienceRules == null || audienceRules.Count == 0)
  29:         return "The audience contains no rules.";
  30:  
  31:     string rulesXml = "<rules>\r\n";
  32:     foreach (AudienceRuleComponent rule in audienceRules)
  33:     {
  34:         if (includeAllAttributes)
  35:         {
  36:             rulesXml += string.Format("\t<rule field=\"{1}\" op=\"{0}\" value=\"{2}\" />\r\n", rule.Operator, rule.LeftContent, rule.RightContent);
  37:         }
  38:         else
  39:         {
  40:             switch (rule.Operator.ToLowerInvariant())
  41:             {
  42:                 case "=":
  43:                 case ">":
  44:                 case ">=":
  45:                 case "<":
  46:                 case "<=":
  47:                 case "contains":
  48:                 case "<>":
  49:                 case "not contains":
  50:                     rulesXml += string.Format("\t<rule field=\"{1}\" op=\"{0}\" value=\"{2}\" />\r\n", rule.Operator, rule.LeftContent, rule.RightContent);
  51:                     break;
  52:                 case "reports under":
  53:                 case "member of":
  54:                     rulesXml += string.Format("\t<rule op=\"{0}\" value=\"{1}\" />\r\n", rule.Operator, rule.RightContent);
  55:                     break;
  56:                 case "and":
  57:                 case "or":
  58:                 case "(":
  59:                 case ")":
  60:                     rulesXml += string.Format("\t<rule op=\"{0}\" />\r\n", rule.Operator);
  61:                     break;
  62:             }
  63:         }
  64:     }
  65:     rulesXml += "</rules>";
  66:     return rulesXml;
  67: }

The help for the command is shown below:

C:\>stsadm -help gl-enumaudiencerules

stsadm -o gl-enumaudiencerules


Outputs the rules for an audience.

Parameters:
        -name <audience name>
        [-ssp <SSP name>]
        [-explicit (shows field and value attributes for every rule)]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-enumaudiencerules MOSS 2007 8/6/2008

Parameter Name Short Form Required Description Example Usage
name n Yes The name of the audience -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
explicit ex No If specified then all attributes will be returned in the XML - this is only really useful if you wish to see the field names of the special operators, "member of" and "reports under". -explicit

-ex

The following is an example of how to display the rules associated with an audience:

stsadm -o gl-enumaudiencerules -name "IT Department"

The following is an example of the XML that would be displayed:

   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>

7Aug/081

Assigning Rules to Audiences via STSADM

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:

  1. new AudienceRuleComponent(null, "(", null);
  2. new AudienceRuleComponent("Department", "=", "IT");
  3. new AudienceRuleComponent(null, "OR", null);
  4. new AudienceRuleComponent("Everyone", "Reports Under", "domain\glapointe");
  5. new AudienceRuleComponent(null, ")", null);
  6. new AudienceRuleComponent(null, "AND", null);
  7. 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>
You could easily pass this same XML structure in as a parameter by removing the line breaks and replacing the quotes with tick marks.  By using this simple XML approach I was able to write the code very quickly.  The only part that tripped me up was the "member of" operation.  The value of this operation must be a fully distinguished AD name and not the login name as depicted - but I wanted to be able to use just the login name.  What I found was that the API provides a handy little method for converting the login name (you can also pass in an email address) to a distinguished name, as seen in this snippet:
   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;
The complete code for the command can be seen below:
   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

7Aug/082

Creating Audiences via STSADM

The project that I'm currently working has a need to create a ton of audiences representing divisions, departments, locations, etc.  We wanted to be able to include the creation of these audiences in our install script so that we easily recreate the audiences in various environments.

To accomplish this task there were two core things I had to figure out - the first was how to create the actual audience, this was the easy part.  The second was how to assign rules to that audience - I'll discuss this problem in my next post.

Creating an audience programmatically turned out to be real easy - you basically just get an instance of the AudienceManager class by passing in an SSP context into the constructor and then you use the Audiences property (which is of type AudienceCollection) and call the Create method to create the audience.  The Create method returns back an Audience object which you can then use to set additional properties such as the owner (OwnerAccountName) and the group operation (GroupOperation).  You then call Commit to save the changes.

The GroupOperation property probably needs some additional explanation.  The value it takes is an AudienceGroupOperation enum which consists of the following values:

  1. AUDIENCE_AND_OPERATION
  2. AUDIENCE_OR_OPERATION
  3. AUDIENCE_MIX_OPERATION
  4. AUDIENCE_NOGROUP_OPERATION

This property defines how the audience rules are handled.  If you specify AUDIENCE_AND_OPERATION then every rule specified will have to result in a match for an item to be returned.  If you specify AUDIENCE_OR_OPERATION then if any of the rules matched for an item then it will be returned.  This is consistent with the behavior you get when creating rules via the browser.  If you set the value to AUDIENCE_NOGROUP_OPERATION then you are saying there is no grouping, or more specifically there will be only one rule.

Here's the one that may surprise some people (it did me) - the AUDIENCE_MIX_OPERATION basically says that I want to create a complex grouping of rules - so something like '(Department == "IT" and Reports Under "John Smith") or Hero == true'.  That's great right?  You can build audiences with complex rules so you can finally get creative - here's the catch: once you do this you can no longer manage those rules via the browser - the only thing you'll be able to do is compile the rule and view memberships - you won't even be able to delete it.  But don't worry - I've got you covered - stay tuned for future posts!  One more thing - you can't set the GroupOperation property to AUDIENCE_MIX_OPERATION - this value will be returned if your rules are complex but you can't preset the value.  I found the best thing to do is to just not set this property at all - but I did choose to expose it in the command I created which I called gl-createaudience.

Here's a snip of the core code that you need to create the actual audience (again, adding rules will be covered in my next post):

   1: public static void Create(string sspName, string audienceName, string description, RuleEnum rule, string owner)
   2: {
   3:     ServerContext context;
   4:     if (string.IsNullOrEmpty(sspName))
   5:         context = ServerContext.Default;
   6:     else
   7:         context = ServerContext.GetContext(sspName);
   8:  
   9:     AudienceManager manager = new AudienceManager(context);
  10:     AudienceCollection auds = manager.Audiences;
  11:  
  12:     if (auds.AudienceExist(audienceName))
  13:     {
  14:         throw new SPException("Audience name already exists");
  15:     }
  16:     Audience audience = auds.Create(audienceName, description??"");// IMPORTANT: the create method does not do a null check but the methods that load the resultant collection assume not null.
  17:     if (rule == RuleEnum.Any)
  18:         audience.GroupOperation = AudienceGroupOperation.AUDIENCE_OR_OPERATION;
  19:     else if (rule == RuleEnum.All)
  20:         audience.GroupOperation = AudienceGroupOperation.AUDIENCE_AND_OPERATION;
  21:  
  22:     if (!string.IsNullOrEmpty(owner))
  23:     {
  24:         audience.OwnerAccountName = owner;
  25:     }
  26:     else
  27:     {
  28:         audience.OwnerAccountName = string.Empty;
  29:     }
  30:     audience.Commit();
  31: }

The help for the command is shown below:

C:\>stsadm -help gl-createaudience

stsadm -o gl-createaudience


Creates an audience.

Parameters:
        -name <audience name>
        [-ssp <SSP name>]
        [-description <audience description>]
        [-owner <DOMAIN\user>]
        [-rule <any | all | mix> (default is any)]
        [-update (if the audience exists, update it)]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-createaudience MOSS 2007 Released: 8/6/2008
Updated: 8/14/2008

Parameter Name Short Form Required Description Example Usage
name n Yes This is the name of the audience. -name "IT Department"

-n "IT Department"
ssp   No The name of the SSP to create the audience within.  If not specified then the default SSP is used. -ssp SSP1
description desc No A brief description of the audience.  I like to provide a text version of the rules that are applied.  This makes it easier to manage via the browser especially when complex rules are used. -description "All employees who are members of the IT Department: Department==IT or Reports Under==John Smith"

-desc "All employees who are members of the IT Department: Department==IT or Reports Under==John Smith"
owner   No The login name of the user who will be assigned as the owner of the audience.  Though not required it's always recommended to identify someone as the person who is responsible for the logic behind the rules. -owner "domain\user1"
rule r No One of the following values: Any, All, Mix.  If Mix is specified it behaves the same as though the parameter were omitted. -rule Any

-r Any
update u No If specified then the existing audience, if found, will be updated.  This is useful when using the command in the context of a script that you'd like to be able to run repeatedly without having to delete the audience and therefore have to reset any items using that audience. -update

-u

The following is an example of how to create an audience named "IT Department":

stsadm -o gl-createaudience -name "IT Department" -description "All employees of who are members of the IT Department: (Department==IT or Reports Under==John Smith) and IsContractor==false" -rule mix -owner domain\user1

Update 8/14/2008: I've added an -update parameter to allow updating the audience if it already exists.

30Apr/082

Programmatically Setting Web Part Audience Targeting

I've been doing some work on my gl-exportlistitem2 and gl-addlistitem commands so that I can support the import of web part pages.  I thought I was about done until I discovered that I had an issue with pages and web parts that were using audience targeting.  There was actually two issues - one was that when I imported a page to another farm the GUID used to store the audience didn't match up so it would lose its setting for the page.  The other issue was that some web parts (specifically anything that is not a V2 web part) do not export the AuthorizationFilter property which is where the audience settings are stored.  I find this very odd that they chose to omit this property when exported.  Note that for V2 web parts the audience information is exported because it is stored in the IsIncludedFilter property of the web part which has been marked as obsolete (internally this property just references the AuthorizationFilter property).

When I set out to do this I thought it would be pretty easy - just store the name with the audience ID and store the configuration information in a MetaData element that I have which I could then use to set the property during the import.  The problem that I ran into was the format of the AuthorizationFilter property - it's just not documented anywhere!  If you view the MSDN documentation for the property it says the following:

The Web Part infrastructure does not implement any default behavior for the AuthorizationFilter property. However, the property is provided so that you can assign an arbitrary string value to a custom Web Part. This property can be checked by SPWebPartManager during its AuthorizeWebPart event to determine whether the control can be added to the page.

The thing is - there's nothing arbitrary about this property - it has a very explicit format which if you don't follow will result in the web part failing to process the audience settings - the format of this matches the format of the Audience field of the web part page - if you mess that field up you will get an error when trying to get into the page settings.

So what is the format of the property?  First you need to understand that there are three types of audience information that the property is storing: Global Audiences created via the SSP; Distribution Lists; SharePoint Groups.  Each of these values is stored slightly differently and the order of their appearance is important.  Each item is delimited with two semi-colons: ";;".

The global audience is stored as a GUID and is the only type of item that can be stored without any delimiter information if no other items exist.  Multiple items are separated with a comma.

The distribution list is stored as an LDAP string.  Multiple items are separated with a new line character ("\n").

The SharePoint groups are stored as a named value (i.e., "Members", "Owners", etc.) and multiple items are separated with a comma.

So putting it all together you would construct a string containing one or more elements of each like so:

   1: string[] globalAudienceIDs = new string[] {"e4687e64-c9d8-4860-bbc3-ec036bf9915d"};
   2: string[] dlDistinguishedNames = new string[] {"cn=group1,cn=users,dc=spdev,dc=com", "cn=group2,cn=users,dc=spdev,dc=com"};
   3: string[] sharePointGroupNames = new string[] {"Demo Members", "Demo Owners"};
   4:  
   5: string result = string.Format("{0};;{1};;{2}",
   6:                               string.Join(",", globalAudienceIDs),
   7:                               string.Join("\n", dlDistinguishedNames),
   8:                               string.Join(",", sharePointGroupNames));

That being said - if you can avoid hard coding this formatting than you should.  So how do you avoid it - you use the GetAudienceIDsAsText method of the AudienceManager class.  To use this method you would do something like the following:

   1: string[] globalAudienceIDs = new string[] {"e4687e64-c9d8-4860-bbc3-ec036bf9915d"};
   2: string[] dlDistinguishedNames = new string[] {"cn=group1,cn=users,dc=spdev,dc=com", "cn=group2,cn=users,dc=spdev,dc=com"};
   3: string[] sharePointGroupNames = new string[] {"Demo Members", "Demo Owners"};
   4:  
   5: string result = AudienceManager.GetAudienceIDsAsText(globalAudienceIDs, dlDistinguishedNames, sharePointGroupNames);

What's cool is that there is a reciprocal method called GetAudienceIDsFromText which will give you the IDs based on the string.

In my particular case I wasn't able to use these and had to know the format of the string because I needed to supplement the GUIDs with the named value so that I could do an import in a different farm.

Note that this same format is used for the Audience field of a publishing page.