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 thoughts on “Importing Audiences using STSADM”
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
Crap – thanks for the info – I’ve got a fix for this which I will push out tonight. Thanks again!