Migrating Audience Targeted Information

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

Sometimes you’ll encounter a scenario where you need to move a site from one environment to another and the site is using Audiences. Now I’m personally a bit of a fan of Audiences for simple out of the box targeting of information. However, it has one pretty major flaw. Audiences are fundamentally environment specific. There is no out of the box method for remapping or moving audience targeted information and have it still work properly on the other side. This is due to a number of reasons which I’m not going to go in to in this blog post. However, here is a tool that can help with this.

This tool is pretty straight forward with only two command, import and export:

  • Export—Generates a file with a mapping of audiences to be used when updating the new environment.
  • Import—Based on your mapping file updates the content in the new environment to use the new environments audiences.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Server;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Navigation;
using Microsoft.Office.Server.WebControls.FieldTypes;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.Office.Server.Audience;
using System.IO;

namespace WithinSharePoint.MigrateAudiences
{
class Program
{
internal static List Audiences = null;
internal static Dictionary<string, Audience> RemappedAudiences = null;
static void Main(string[] args)
{
try
{
if (args.Count() == 2)
{
SPSite site = new SPSite(args[1]);
foreach (Audience a in GetAudiences(site))
{
Console.WriteLine(a.AudienceName + “,”+ a.AudienceID.ToString());
}
}
if (args.Count() == 3)
{
SPSite site = new SPSite(args[1]);
SPWeb RootWeb = site.OpenWeb();
Audiences = GetAudiences(site);
RemappedAudiences = AudienceMap(args[2]);
ScanWeb(RootWeb);
}
else if ((args.Count() < 2) | (args.Count() > 3))
{
WriteUsage();
}
}
catch(Exception ex)
{
Console.WriteLine(“ERROR:” + ex.Message);
WriteUsage();
}
}

internal static void WriteUsage()
{
Console.WriteLine(“Usage: WithinSharePoint.MigrateAudiences -EXPORT [URL TO RETRIEVE AUDIENCES] >> AudienceExport.csv”);
Console.WriteLine(“Usage: WithinSharePoint.MigrateAudiences -IMPORT [URL TO SCAN AND UPDATE] [FILEPATH]“);
Console.WriteLine(“File is a text document with this format (Audience ID is the ID of the audience in the SOURCE environment, not the destination):”);
Console.WriteLine(“AudienceName,AudienceID”);
}

internal static void ScanWeb(SPWeb web)
{
web.AllowUnsafeUpdates = true;
ScanLists(web.Lists);
try
{
ScanNavigation(web.Navigation.GlobalNodes);
ScanNavigation(web.Navigation.QuickLaunch);
}
catch (Exception ex)
{
Console.WriteLine(“Error updating navigation. ” + ex.Message);
Console.WriteLine(web.Url);
}
foreach (SPWeb child in web.Webs)
{
ScanWeb(child);
}
web.AllowUnsafeUpdates = false;
}

internal static void ScanLists(SPListCollection Lists)
{
foreach (SPList list in Lists)
{
if (list.Fields.ContainsField(“Target_x0020_Audiences”))
{
ScanItems(list.Items, “Target_x0020_Audiences”);
}
else if (list.Fields.ContainsField(“Audience”))
{
ScanItems(list.Items, “Audience”);
}
}
}

///

/// Scans and updates all audience targetted sharepoint navigation nodes with audiences from the new environment
///

///internal static void ScanNavigation(SPNavigationNodeCollection Nodes)
{
string value = “”;
string[] values;
bool pendingupdate = false;
Char[] splitter = new Char[] { ‘;’ };
SPNavigationNode node;
for (int i = 0; i < Nodes.Count; i++ )
{
node = Nodes[i];
string newvalue = “”;
if (node.Properties.Contains(“Audience”))
{
value = node.Properties["Audience"].ToString();
value = value.Replace(‘,’, ‘;’);
values = value.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
foreach (string val in values)
{
if (RemappedAudiences.ContainsKey(val))
{
//update with new audiences
pendingupdate = true;
newvalue += RemappedAudiences[val].AudienceID + “,”;
}
else
{
//this is to preserve existing unknown audiences
newvalue += val + “,”;
}
}
if (pendingupdate)
{
node.Properties["Audience"] = newvalue;
node.Update();
}
}
}
}

///

/// Scans all items in an audience targetted list and updates them with new environments audiences
///

//////internal static void ScanItems(SPListItemCollection items, string AudienceField)
{
Console.WriteLine(“Scanning and updating list ” + items.List.Title);
bool ListUpdate = false;
Char[] splitter = new Char[] { ‘;’ };
SPListItem item;
for(int i = 0; i < items.Count;i++)
{
try
{
item = items[i];
string value = “”;
if(item[AudienceField] != null)
{
if (!String.IsNullOrEmpty(item[AudienceField].ToString()))
{
bool PendingUpdate = false;
string NewValue = “”;
value = item[AudienceField].ToString();
if (value.Contains(“,”))
{
value = value.Replace(‘,’, ‘;’);
}
//Console.WriteLine(value);
string[] audiences = value.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
foreach (string a in audiences)
{
//Console.WriteLine(a);
if (RemappedAudiences.ContainsKey(a))
{
//add remapped audience to update
PendingUpdate = true;
NewValue += RemappedAudiences[a].AudienceID + “,”;
}
else
{
//keep unknown audiences in the item
NewValue += a + “,”;
}
}
if (PendingUpdate)
{
//don’t ask why sharepoint uses csv for audience id’s and then appends ;;; at the end
item[AudienceField] = NewValue + “;;;;”;
ListUpdate = true;
item.UpdateOverwriteVersion();
}
}
}
}
catch(Exception ex)
{
Console.WriteLine(“Error reading line item:” + ex.Message);
Console.WriteLine(items[1][AudienceField].ToString());
Console.WriteLine(ex.StackTrace);
}
}
if (ListUpdate)
{
items.List.Update();
}
}

///

/// Reads the contents of the audience export file to generate a mapping of equivalent audiences in the target environment
///

//////
/// String – AudienceID of Source Environment
/// Audience – Audience in New/Target Environment
///
internal static Dictionary<string, Audience> AudienceMap(string FilePath)
{
Dictionary<string, Audience> map = new Dictionary<string, Audience>();
StreamReader reader = new StreamReader(FilePath);
string input = null;
while ((input = reader.ReadLine()) != null)
{
string[] line = input.Split(‘,’);
if(line.Count() == 2)
{
var match = from a in Audiences where a.AudienceName == line[0] select a;
foreach (Audience m in match)
{
map.Add(line[1], m);
}
}
}
return map;
}

///

/// Returns a list of all audiences from the target site collection
///

//////
internal static List GetAudiences(SPSite site)
{
List audiences = new List();
SPServiceContext context = SPServiceContext.GetContext(site);
AudienceManager aMan = new AudienceManager(context);
foreach (Audience a in aMan.Audiences)
{
audiences.Add(a);
}
return audiences;
}
}
}

Importing Profiles in SharePoint

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

A common need when testing SharePoint solutions is having test accounts setup a certain way so they can do something. Whether this is a test account for a country, language, etc. Normally it’s painful to set up these accounts as typically they end up being setup by hand in the user profile service application; therefore I’ve gone ahead and built a tool which  reads all the attributes for a profile from a CSV and updates the users profiles (code below). This also works for normal accounts if you’re trying to import in bulk via Excel or some other data source for a one time import which can happen during migrations from other systems.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.SharePoint;
using Microsoft.Office.Server;
using Microsoft.Office.Server.UserProfiles;
using System.Web;

namespace WithinSharePointUpdateUserProfile
{
class Program
{
static Char[] Splitter = new Char[] { ‘,’ };
static UserProfileManager uMan = null;
static Dictionary<UserProfile,Dictionary> UsersToUpdate = new Dictionary<UserProfile,Dictionary>();

static void Main(string[] args)
{
if (args.Count() == 1)
{
SPSite site = new SPSite(args[0]);
DisplayProfileAttributes(SPServiceContext.GetContext(site));
}
else if (args.Count() == 2)
{
try
{
SPSite site = new SPSite(args[0]);
uMan = UserManager(SPServiceContext.GetContext(site));
ReadFile(args[1]);
foreach (UserProfile K in UsersToUpdate.Keys)
{
UpdateUser(K, UsersToUpdate[K]);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Help();
}
}
else
{
Help();
}
}

static void DisplayProfileAttributes(SPServiceContext Context)
{
UserProfileConfigManager config = new UserProfileConfigManager(Context);
ProfilePropertyManager pMan = config.ProfilePropertyManager;
CorePropertyManager cMan = pMan.GetCoreProperties();
foreach (CoreProperty prop in cMan.PropertiesWithSection)
{
Console.WriteLine(“Display Name : ” + prop.DisplayName + ” Internal Name : ” + prop.Name);
}
}

static void Help()
{
Console.WriteLine(“Usage is: WithinSharePointUpdateUserProfile http://WEBAPPURL C:\\Path\\To\\File”);
Console.WriteLine(“To display a list of profile fields: WithinSharePointUpdateUserProfile http://WEBAPPURL&#8221;);
Console.WriteLine(“File Format is:”);
Console.WriteLine(“USERNAME,CORP\\Username,FIELDNAME,FIELDVALUE”);
}

static UserProfileManager UserManager(SPServiceContext Context)
{
return new UserProfileManager(Context, true);
}

static void ReadFile(string Path)
{
StreamReader reader = new StreamReader(Path);
string input = null;
UserProfile UP;
Dictionary User;
while ((input = reader.ReadLine()) != null)
{
User = UpdatedAttributes(input, out UP);
UsersToUpdate.Add(UP, User);
}
}

static bool UpdateUser(UserProfile Profile, Dictionary ProfileChanges)
{
Console.WriteLine(“Updating user: ” + Profile.DisplayName);
try
{
foreach (string k in ProfileChanges.Keys)
{
if (Profile[k] != null)
{
Profile[k].Value = ProfileChanges[k];
Profile[k].Privacy = Privacy.Public;
}
}
Profile.Commit();
}
catch (Exception ex)
{
Console.WriteLine(“Error updating profile ” + Profile.DisplayName);
Console.WriteLine(ex.Message);
return false;
}
return true;
}

static UserProfile GetProfile(string Username)
{
return uMan.GetUserProfile(Username);
}

static Dictionary UpdatedAttributes(string row, out UserProfile Profile)
{
Dictionary result = new Dictionary();
string[] split = row.Split(Splitter);
Profile = GetProfile(split[1]);
for (int i = 2; i < split.Count(); i++)
{
result.Add(split[i], split[i + 1]);
i++;
}
return result;
}
}
}

SharePoint and Chrome

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

A few months ago I posted an entry called SharePoint 2010 Scrolling detailing a method to get scrolling working on less supported browsers in SharePoint 2010. Along with some background on to why the method works.

I’ve had reason recently to revisit Chrome and SharePoint compatibility specifically as of late. In the process a different fix came out of it. One that is less heavy-handed. But more importantly I found out that the cause of the scrolling inconsistency on some browsers like iOS Safari and Chrome is not due to the causes that have been previously documented by other bloggers or myself. The cause is, in fact, a timing issue around execution of a specific bit of very important onload JavaScript.

The bit that doesn’t execute (and causes a systemic issue, one of the issues it causes is the scrolling weirdness), is: Read more of this post

Turning Quick Launch into a Tab control

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

First, let me start this post by saying that I absolutely love that navigation in SP2010 is completely standard HTML. This is a ‘codeless’ method for turning that left navigation quick launch in to a nifty tab control. The use case for this was that for some landing pages people wanted tabs rather than quick launch but for everything else the normal quick launch. So how do you change the quick launch as needed to tabs? The answer is pretty simple.

Before we get into the details of how to build this, let’s take a step back and look at what the quick launch HTML looks like: Read more of this post

SharePoint Search with Pagination

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

This is an example class for showing how to search SharePoint with paging support in the API. This function accepts a string containing the standard SharePoint SQL style search query:

ie: SELECT TOP 50 AccountName, Size, Rank, Path, Title, Description, Write FROM portal..scope() WHERE “scope” = ‘People’ AND CONTAINS (“LastName”,’”Doe”‘)   ORDER BY “AccountName” DESC

To page you can use the optional int rowCount and page overloads. Whenever page is specified it judges the start row as rowCount*page. So page 10 begins at row 500 if rowCount is 50. Read more of this post

Using Native SharePoint Form Fields

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

When making a generic web part for modifying or interacting with different SharePoint data you can’t always predict the column data type before hand.

The best way to make sure your web part works regardless of what data is being tossed in to it is to just use the out of box SharePoint Form Field control. This control goes ahead and grabs the column edit and display templates depending on the state you specify.

Below is a code snippet example of how to use this control. Takes a lot of the headache out when dealing with unknown data types/building generic web parts.

Read more of this post

Data View Web Part Tricks

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

One of the most handy out of the box customization components in my opinion is the Data View Web Part, but mucking around in XSLT can be a pain at times so here’s some reference points for making things easier, these work with SharePoint 2007/2010 and anything else that can use XSLT.

The first snippet is for pulling unique values down from your data source initially. The second is the much more fun way to make it so the DVWP filter drop downs are unique per column. The filter drop down unique value code snippet is put in to the template and is field type independent. Next we have an XSLT template for changing a string to be all uppercase to make a query comparison case-insensitive effectively, it can also be used for always showing a field as uppercase.

I also pasted some of my older XSLT tidbits related to truncated strings for Read More links and proper string extraction for rendering url and image fields. Read more of this post

Accordion Data View Web Part

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

Even more ‘fun’!

Making nifty things with jQuery with the data view web part.

First component is a jQuery powered faked accordion script with highlight of mouse over.

A real accordion will not work in DVWP due to how the markup is output.

Requires:
jQuery WebUI plugin

Here’s the code: Read more of this post

Multiple Entry Single Field

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

This is a method I came up with to use jQuery to fake an n+1 relationship for one or more columns within a single list item. You can see in the image below that the user is initially presented with the designated fields empty; they have a button for adding the new ‘rows’ in to the fields specified. This is a simple method for basic scenarios where you need a relational style input within a single form.

The code works by adding an event handle to each new text box inserted that is triggered on KeyUp to concatenate the values into a CSV list that is stored in the hidden original field. So if you have added three ‘rows’ you would get something like this saved: “Value1,Value2,Value3,”

To use, insert a content editor web part and edit the [title=”Column” selector to be the display value of the field to treat in this manner.

Read more of this post

Force Page Edit Mode in SharePoint

Slalom Consultant Maarten Sundman

Slalom Consultant Maarten Sundman specializes in .NET, SharePoint, and Silverlight solutions and has experience in the Financial Services, Software, and Utilities and Energy sectors.

by Maarten Sundman

On some SharePoint pages there is no simple way to get to the edit web page view to add/modify/remove web parts. However, SharePoint accepts a QueryString for opening a page in edit mode:

HTTP:\\MYSHAREPOINT?ToolPaneView=2&pagemode=edit

By adding this to the end of any URL where you have rights to edit the page you can then go ahead and edit the page to your hearts content. This is particularly useful when doing modifications to NewForm/EditForm without dissociating the page, or making your own form page.

- Maarten

Follow

Get every new post delivered to your Inbox.

Join 130 other followers

%d bloggers like this: