MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Friday, April 24, 2009

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>

2 comments:

Yorick said...

Hi Gary,

Brilliant tools!! Keep up the good work!!

Just one remark for the Audience Import/Export commands:
When you use the export audience tool to export an audience that uses a Member Of rule, the group names are exported by their DN. The import however expects the CN of the group, not the DN. This really got me for a couple of hours :-)

CU!

Yorick

Gary Lapointe said...

Crap - thanks for the info - I've got a fix for this which I will push out tonight. Thanks again!