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 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

Fun with PowerShell Recently

Slalom Consultant Derek Martin

Slalom Consultant Derek Martin is an accomplished Microsoft systems developer and integrator, experienced in developing and deploying SharePoint and CRM solutions, integrating line of business applications, and leveraging existing infrastructure investments.

Normally, I try to keep my blog high level, in the architect space. However, I am currently assigned to a client and they were needing a migration script to convert accounts in SharePoint from AD to LDAP for about 100,000 accounts. The following is what I threw together in about 10 minutes from a few internet posts that I discovered and did the trick. There are far better ways of doing this I am sure, especially in the 2010 version, but this little gem worked wonders – albeit it took a while to run (28 hours).

$strFilter = “User”
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = “LDAP://”
$objSearcher.SearchScope = “Subtree”
$objSearcher.PageSize = 1000
$objSearcher.PropertiesToLoad.Add(“sAMAccountName”)
$objSearcher.PropertiesToLoad.Add(“distinguishedName”)
$objSearcher.PropertiesToLoad.Add(“EmployeeId”)

Read more of this post

Follow

Get every new post delivered to your Inbox.

Join 129 other followers

%d bloggers like this: