Feeds:
Posts
Comments

Archive for March, 2013

Spring cleaning is coming early to my SharePoint 2010 farm. With SharePoint 2013 right around the corner and a huge push to cleanup farms before doing the upgrade, I’ve taken the initiative to take out the garbage which has been accumulating for several years. I’m finding all sorts of rubbish, too: Fab 40 (yes, we still had remnants of it installed and running in SharePoint 2010); other no-longer used or supported features; unused sites; unused SharePoint groups; groups with users who no longer exist; and even audiences that were created and never used. Just like at home where I’m doing the winter pruning and de-cluttering the basement, the garbage and recycle cans have been filled to the brim many a time during this cleanup project. Hopefully, I’ll soon be able to blog about removing some of the other rubbish mentioned, but today I want to focus on those unused audiences.

I’ve ran across several questions posted on various forums about finding where audiences are being used or even how they are stored, but the answers given are generally missing or, at best, vague. Being that I needed a real solution, I got to task. The answer wasn’t simple: Audiences are used and stored in three different ways (that I’ve been able to identify–please let me know if I missed any!):

  1. You can specify an audience on a web part.
  2. You can set an audience on a list item.
  3. You can designate an audience for a navigation item (i.e. Top Nav Bar, Quick Launch, Global Navigation, or Current Navigation).

Thus, to find where an audience is used, you have to check all of these different areas. Not even the databases store this type of data in one place (see “Where Target Audience settings data is stored in SharePoint 2010?” on the Microsoft forum for more info on the storage of a SPListItem’s audience).

At this point, I’ve got to say, “I love PowerShell!” It comes to the rescue here, yet again. (Without it, I’d be relegated to writing a console app.) After studying and understanding the below PowerShell script, copy and paste it into a file (I named mine “Get-AudienceTargets.ps1” and saved it on the desktop).

Param(
	[parameter(Mandatory=$True,Position=1)]
	[String]
		$WebApplicationURL
)

if((Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction
SilentlyContinue) -eq $null){
    $ver = $host | select version
    if ($ver.Version.Major -gt 1) {
        $Host.Runspace.ThreadOptions = "ReuseThread"
    }
    Add-PsSnapin Microsoft.SharePoint.PowerShell
    Set-location $home
}

$psShared = [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared;

$allSites = Get-SPWebApplication $WebApplicationURL | Get-SPSite -Limit ALL;

[Void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.Audience");
$spcontext = [Microsoft.Office.Server.ServerContext]::GetContext($allSites[0]);
$audmanager = New-Object Microsoft.Office.Server.Audience.AudienceManager($spcontext);
$audiences = $audmanager.Audiences;

function GetAudienceNames([string]$audienceFilter){
    $au = $audienceFilter.Split(";;");
    if($au[0] -ne "") {
        $au[0].Split(',') | foreach-object { 
            if($audiences.AudienceExist([Guid]$_)) {
                $audienceFilter = $audienceFilter.Replace($_, $audiences[[Guid]$_].AudienceName); # $r += $audiences[[Guid]$_].AudienceName + ", ";
            }
        }
    }
    return $audienceFilter.Replace(";;","|");
}

function GetLWPM($w, [string]$url){
    try {
        $wpm = $w.GetLimitedWebPartManager($url, $psShared);
    }
    catch {
        write-host ("Error getting limited web part manager for " + $url) -foreground red
    }
    return $wpm;
}

write-output ("Web|Path|Web Part Title|SharePoint Audience|Audience AD LDAP Path|SharePoint Group");

$allSites | get-spweb -limit all | foreach-object {
	$w = $_;
    try {
	$w.Files | where-object {$_.ServerRelativeUrl -like "*.aspx"} | foreach-object {
		$wpm = GetLWPM -w $w -url $_.ServerRelativeUrl; #$w.GetLimitedWebPartManager($_.ServerRelativeUrl, $psShared);
        if($wpm){
            try{
		      $wpm.WebParts | where-object {$_.AuthorizationFilter -gt ""} | foreach-object {
		          write-output ($w.Url + "|" + $_.ServerRelativeUrl + "|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.AuthorizationFilter));
		      }
            } finally {
                $wpm.Dispose();
            }
        }
	}
	$w.Lists | where-object { !$_.Hidden } | foreach-object {
        if($_ -is [Microsoft.SharePoint.SPDocumentLibrary]){
    		$_.Items | where-object {$_.Url -like "*.aspx"} | foreach-object {
                $item = $_;
                $iUrl = ($w.Url + "/" + [System.Web.HttpUtility]::UrlPathEncode($_.Url));
    			$wpm = GetLWPM -w $w -url $iUrl; # $w.GetLimitedWebPartManager($iUrl, $psShared);
                if($wpm){
                  try{
    			    $wpm.WebParts | where-object {$_.AuthorizationFilter -gt ""} | foreach-object {
    			 	   write-output ($w.Url + "|" + $item.Url + "|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.AuthorizationFilter));
    			    }
                  } finally {
                    $wpm.Dispose();
                  }
                }
            }
        }
        if ($_.Fields.ContainsFieldWithStaticName("Target_x0020_Audiences")) {
            $_.Items | foreach-object {
                if ($_["Target_x0020_Audiences"] -ne $null){
                    write-output ($w.Url + "|" + $_.Url + "|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_["Target_x0020_Audiences"]));
                }
            }
        }
	}
    if([Microsoft.SharePoint.Publishing.PublishingWeb]::IsPublishingWeb($w)){
        $pweb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($w);
        $pweb.Navigation.GlobalNavigationNodes | where-object { $_.Properties.ContainsKey("Audience") -and $_.Properties["Audience"] -ne "" } | foreach-object { 
            write-output ($w.Url + "|[GlobalNavigationNodes]|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.Properties["Audience"]))
        }
        $pweb.Navigation.CurrentNavigationNodes | where-object { $_.Properties.ContainsKey("Audience") -and $_.Properties["Audience"] -ne "" } | foreach-object { 
            write-output ($w.Url + "|[CurrentNavigationNodes]|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.Properties["Audience"]))
        }
    } else {
        $w.Navigation.QuickLaunch | where-object { $_.Properties.ContainsKey("Audience") -and $_.Properties["Audience"] -ne "" } | foreach-object { 
            write-output ($w.Url + "|[QuickLaunch]|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.Properties["Audience"]))
        }
        $w.Navigation.TopNavigationBar | where-object { $_.Properties.ContainsKey("Audience") -and $_.Properties["Audience"] -ne "" } | foreach-object { 
            write-output ($w.Url + "|[TopNavigationBar]|" + $_.Title + "|" + (GetAudienceNames -audienceFilter $_.Properties["Audience"]))
        }
    }
    } finally {
        $w.Dispose();
    }
}

Next, execute the script from a PowerShell command prompt (the astute may notice this doesn’t necessarily have to be the SharePoint Management Shell–when writing scripts, I like to use the PowerShel ISE but it doesn’t normally load the SharePoint snapin), supplying the URL of the web application you want to check.

The script will iterate through all of the content in the web application and output pipe-delimited (‘|’) text with a header and the data for the audience data it finds such as:

Web|Path|Web Part Title|SharePoint Audience|Audience AD LDAP Path|SharePoint Group
http://sppaule|Shared Documents/Test-WebPartPage.aspx|Shared Documents|Admins||
http://sppaule|SitePages/Test-WikiPage.aspx|Shared Documents|Admins||
http://sppaule/blank|Lists/Tasks/2_.000|Test2|Admins||
http://sppaule/sites/hp|Pages/default.aspx|Content Editor|7788a621-ec5a-4656-8976-537e29a6d85c,0223cb9a-f465-4598-ae0f-84bc590e0b53,eab8c97d-fcdf-427a-93f7-a929ce24a589,320bc09a-31fb-4350-87d1-37d2cdffaf01||
http://sppaule/sites/hp|Pages/default.aspx|Utah Weather|Admins||
http://sppaule/sites/hp|[GlobalNavigationNodes]|Dev||CN=Team PaulE,OU=Distribution Groups,OU=Corp,DC=ewert,DC=fam
CN=Gen1,OU=Distribution Groups,OU=Corp,DC=ewert,DC=fam|Gen2
http://sppaule/sites/hp|[GlobalNavigationNodes]|HR|0a16b96d-b445-4f05-a6fd-0db9a31fcbad||
http://sppaule/sites/hp|[GlobalNavigationNodes]|Teams|0a16b96d-b445-4f05-a6fd-0da9d31fcbad||
http://sppaule/sites/hp|[CurrentNavigationNodes]|Lists|Admins||

There you have it: A listing of where audiences are used! Those GUIDs that are remaining in the “SharePoint Audience” column of the output are audiences that no longer exist. You’ll need to use the information provided to navigate to the content and update the audience settings for that object (list item, navigation item, or web part).

I like to capture the output to a text file (in the case below, the script will check the web application at http://sppaule for audiences and output will be stored in a file named “audTargets.txt” on my desktop):

PS C:\Users\sppaule\Desktop> .\Get-AudienceTargets.ps1 http://sppaule > audTargets.txt

Then, I open that file in Microsoft Excel, delimiting the columns on the ‘|’ character for easier reading.

I will note that I get a few “log4net” errors in the console window, but these aren’t included in the captured output and I’m not presently concerned with them because they’re probably coming from some of my other garbage. Also, you probably don’t want to execute this during your peak usage periods. Since it iterates through most of your content, it’s pretty intense and can take a long time to execute on farms with a lot of content.

Happy audience hunting!

Read Full Post »