Feeds:
Posts
Comments

Posts Tagged ‘programmatically’

Radu Tut did a wonderful blog about how to get Search Analytics Reports programmatically in SharePoint 2013. I am quite grateful for his guidance and it’s undoubtedly the most referenced article on the topic (even being referenced in Microsoft blogs and other documentation).

I followed Radu’s guidance and have successfully retrieved usage data from my site collections in 2013, but I’ve discovered a few things I’ve not seen anyone talking about:

  1. It is important to note that the DateTime value you use should be UTC. If not, you will, at some time during the day–depending on your offset–get a “Specified argument was out of the range of valid values” error when trying to get the furthest back data (example: -14 days for Day) if you use Local time. Internally, SharePoint uses DateTime.UtcNow.Date (see reflection of Microsoft.Office.Server.Search.Analytics.AnalyticsItemData in the Microsoft.Office.Server.Search.Applications.dll).
  2. To my surprise, I was able to retrieve usage data from a 2010 site collection in SP2013! Apparently, SharePoint is tracking and compiling this data, but the 2010 UI has no way to interface with the data. Thus, if you need usage data for a 2010 UI site collection in SharePoint 2013, turn to your Devs or PowerShell-savvy Admins: If your site is being crawled, the 2013 usage data you seek is in there!
  3. The time period for SP2013 to compile usage data is fairly lengthy (at least 15 minutes in my tiny dev farm). If you attempt to retrieve the data while SharePoint is compiling it, you’ll get back a big fat ‘0’. For expected results, make sure you’re not retrieving the data while SharePoint is compiling it. 🙂 I believe this compilation is done by a timer job once a day at midnight, by default (I was up pretty late working on my project when I discovered this behavior).

Happy reporting!

Advertisements

Read Full Post »

Any of the following sound familiar?

  • I’m getting an error when viewing my calendar. The error says, “The Web application at [your URL] could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.”
  • My calendar view gives the error, “Unable to find specified web in the given URL – [URL].”
  • When I try to edit my calendar overlay, I get a “File Not Found.” error message.

You’ve probably become a victim of the Calendar Overlay’s propensity to use fully qualified URLs. You’ll likely notice when you see the first error I gave above that the domain name given in the URL does not match the domain name you are browsing the page from. Unfortunately, Calendar Overlay won’t take a relative URL. (From a development standpoint, this only makes sense when you consider that the developer short-cutted his/her code writing. Bad. Just bad. There’s plenty of people talking about this–people who have surely been listened to by Microsoft–but that’s not the point of this blog. So, moving on.)

If you’ve extended your SharePoint web application or changed your Default zone’s domain name at any time, you’ve likely become a victim of the Calendar Overlay fully qualified URLs.

Aside from the errors given above, how can I tell? Using SharePoint Management Shell, take a look at the CalendarSettings property of your views:

$w = Get-SPWeb [your site URL]
$l = $w.Lists["[your list name"]"]
$l.Views | ? { $_.CalendarSettings -ne $null } | % { $_.CalendarSettings }

CalendarSettings is a string object that contains a serialized object (i.e. the string is XML). Within that XML string is a WebUrl property like WebUrl="http://sppaule/Lists/Calendar/calendar.aspx". If the protocol and domain (ex: “http://sppaule”) don’t match the protocol and domain you browse from, you’ve got a problem (which is why you’re here reading this). From what I’ve read, both the protocol and domain must match.

“Okay, so they got me? Now what?” It’s really quite easy to fix. If you understood the PowerShell code above, you’ve probably got a really good idea of how to do it. Following is a script you can save to a .ps1 file and execute to update all the views across your entire web application. Just be patient when running it: there could be a lot of views to iterate through!

[cmdletbinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
Param(
  [parameter(Mandatory=$True,Position=1)]
  [String]
    $currentWebApplicationURL,
  [parameter(Mandatory=$True,Position=2)]
  [String]
    $oldWebApplicationURL
)
PROCESS {
[bool]$do = $pscmdlet.ShouldProcess($currentWebApplicationURL);
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
}
if($do){
  write-host "Updating CalendarSettings..."
  $gc = Start-SPAssignment
  $views = $gc | get-spwebapplication $currentWebApplicationURL | get-spsite -limit all | get-spweb -limit all | % { $_.Lists | % { $_.Views | ? { $_.CalendarSettings -ne $null -and $_.CalendarSettings -like '*'+$oldWebApplicationURL+'*' } }}
  $views | % { $_.CalendarSettings = $_.CalendarSettings.Replace($oldWebApplicationURL, $currentWebApplicationURL); $_.Update() }
  write-host $views.Count "views updated.";
  Stop-SPAssignment -SemiGlobal $gc
} else {
  write-host "Operation cancelled."
}
}

(Notice I include the loading of the SharePoint snapin just in case the file gets executed from a regular PowerShell session or the Windows PowerShell ISE–which I like to use for writing and debugging scripts.)

I saved mine on my desktop as “Update-CalendarSettings.ps1”. Then, simply execute the script like the following:

PS C:\Users\sppaule\Desktop> Update-CalendarSettings.ps1 [currentWebAppUrl] [oldWebAppUrl]

Replace [currentWebAppUrl] with your web app’s Default zone URL (ex: “http://sppaule”) and [oldWebAppUrl] with the protocol and domain that is incorrect in the CalendarSettings (ex: “http://paule”).

Note: This will replace all occurences of [oldWebAppUrl] with [currentWebAppUrl] within your whole CalendarSettings. I couldn’t think of any case where this would be bad for us, but you may know of some for yourself. If so, be sure to modify the Replace(...) as necessary.

A few other tidbits of learning to consider:

  1. If you are using a Microsoft Forefront Unified Access Gateway, you may be saying, “Well, my browser protocol and/or domain don’t match what is in CalendarSettings. Why don’t I have a problem?” I wondered the exact same. It’s because Forefront UAG translates the protocol and domain for you. Your browser may show “https://extranet.sppaule.me”, but the request from UAG to the SharePoint farm is most likely something like “http://sppaule”. When SharePoint responds back, the UAG translates any “http://sppaule” URLs to “https://extranet.sppaule.me”.
  2. It wasn’t until researching this that I realized that ListViewWebPart views are actually stored on the SPList. They’re stored as Hidden views. Makes updating the CalendarSettings WebUrl paths across the board a lot easier.

Read Full Post »

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 »

It’s been far too long since I last blogged. To my followers: my apologies. There have been some great discoveries (particularly in the last few months), but none I have had time to write about, yet. Today, I ran into an intriguing situation that I felt needed to be shared that wouldn’t take long.

Take a look at this screen shot:

Announcements list with two seemingly identical items. Only, one of the items has the "New!" icon.

Announcements list with two seemingly identical items. Only, one of the items has the “New” icon.

Notice anything funny? (Okay, so I gave it away if you looked at the caption.) We’ve got two seemingly identical announcements in this list: both have the same title, creation date, and modification date. Only, one of them is showing the New icon and the other isn’t. I had to take a hard look at this!

Let’s disregard how I got to this for now and focus on the question at hand: Why would one item show the New icon and the other item not? They both show the same Created date and that is clearly not anywhere close the the timeframe for a new item (nearly a whole year earlier). Diving under the hood with PowerShell brought about the answer.

Let’s say $i1 and $i2 are, respectively, the SPListItem objects in PowerShell from the list shown above. My first clue came from inspecting the XML of $i2. There were clearly two dates given for the creation date, as such:

ows_Created='2012-02-29 15:17:19'
ows_Created_x0020_Date='1;#2013-02-20 17:46:57'

Again, let’s disregard how I came about with two different Created dates. It becomes clear that the New icon is going off the Created_x0020_Date field data. Furthering the inspection in PowerShell, we see the following:

PS C:> $f = $i1.Fields.GetFieldByInternalName("Created_x0020_Date")
PS C:> $i1[$f.id]
2013-02-19 16:44:52
PS C:> $i1["Created"]
Wednesday, February 29, 2012 3:17:19 PM

PS C:> $f = $i2.Fields.GetFieldByInternalName("Created_x0020_Date")
PS C:> $i2[$f.id]
2013-02-20 17:46:57
PS C:> $i2["Created"]

Wednesday, February 29, 2012 3:17:19 PM

Viola! Item 1 has a Created_x0020_Date that is outside of the range for a New icon, but Item 2 has a date within the range and thus shows the New icon.

The creation date shown in list views is the Created field where Title = "Created" and InternalName = "Created". While also having Title = "Created", the field used by the New icon is the one with InternalName = "Created_x0020_Date". You never see this later field (Created_x0020_Date) in the UI because it is hidden and readonly.

I’ll discuss how I came about this duplication in my next post, Duplicated list items.

P.S.–
If you need to see or change the duration your web application is set to for the New icon (i.e. how long the New icon will show), use the following PowerShell (copied from Deliveron Blog):

Get The Current Duration To Display The “New” Icon
$webApp = Get-SPWebApplication "http://webAppURL/"
$webApp.DaysToShowNewIndicator

Change The Duration To Display The “New” Icon
# Set to 1 Day
$webApp = Get-SPWebApplication "http://webAppURL/"
$webApp.DaysToShowNewIndicator = 1
$webApp.Update()

Prevent The “New” Icon From Displaying
# Essentially, just set the display duration to 0 days.
$webApp = Get-SPWebApplication "http://webAppURL/"
$webApp.DaysToShowNewIndicator = 0
$webApp.Update()

Read Full Post »

Quite some time ago, I noticed a significant difference between the navigation links that SharePoint adds and the ones that we add through the UI: manually created links were not security trimmed. Last August, I found and responded to a forum question on MSDN which discusses how to add navigation links to the Top Nav which do get security trimmed. I would like to elaborate on this a bit further…

Using the SharePoint 2010 Management Shell, you can add links to the navigation (Top Nav, Quick Launch, and probably any other navigation collection which uses SPNavigationNode) which do get security trimmed. To add to the Top Nav, execute the following (substituting values in the “[…]” blocks as appropriate):

$w = get-spweb [full site path]
$nav = $w.Navigation.TopNavigationBar
$newLink = new-object Microsoft.SharePoint.Navigation.SPNavigationNode -argumentlist @("[description/title]", "[web address]")
$nav.AddAsLast($newLink)

I usually like to add the link at the end of the existing list of links (hence, the use of AddAsLast), but other addition methods (namely Add and AddAsFirst) are also available. Be sure to use a relative path for the web address so the link works across web application zones with different domains. A complete example would look as follows:

$w = get-spweb http://sharepoint/site/site
$nav = $w.Navigation.TopNavigationBar
$newLink = new-object Microsoft.SharePoint.Navigation.SPNavigationNode -argumentlist @("My link", "/site/site/default.aspx")
$nav.AddAsLast($newLink)

Links can similarly be added to the Quick Launch, but with a little twist to accommodate the commonly found node nesting:

$w = get-spweb [full site path]
$nav = $w.Navigation.QuickLaunch
$navNode = ($nav | where-object { $_.Title -eq "[description/title of parent node]" })
$newLink = new-object Microsoft.SharePoint.Navigation.SPNavigationNode -argumentlist @("[description/title]", "[web address]")
$navNode.Children.AddAsLast($newLink)

In getting the parent node, I like to use the Title property, but this can be problematic if you have more than one SPNavigationNode at the root level with the same title. If need be, you can of course use other SPNavigationNode properties to filter the SPNavigationNodeCollection returned to $nav as necessary.

My thanks to Brett Pitstick who was really quite close on what he posted in the MSDN forum question he asked. Though, I still don’t know if this does the trick in Foundation. If anyone finds it does or doesn’t, please let me know.


Follow-up (2/8/2012): A user responded on a Technet SharePoint Forum thread that this does indeed work in SharePoint Foundation 2010 (SPF2010 SP1).

Read Full Post »