Useful piece of Powershell to search for a file(s) / item(s) in the site recycle bin and then restore it/them.

Change the regular expression after match to change what files to search for.

1
2
3
4
5
6
$spsite = (Get-SPSite "http://mysite" )
$files = $spsite.RecycleBin | ?{$_.Title -match "myfile\d{2}"}

foreach ($file in $files) {
Write-Host "Found $($file.Title)" $spsite.RecycleBin.Restore($file.ID)
}

Comment and share

Useful jQuery snippet to add a “View Properties” icon, so the user doesn’t have to use context menu or ribbon (apparently they prefer this)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$(".ms-listviewtable > tbody > tr:first").append("<TH class='ms-vh2 ms-vh2-icon' noWrap>View</TH>");      
$(".ms-listviewtable > tbody > tr").each(function() {        
if ($('td:first', $(this)).hasClass("ms-vb-title")) {
     
var id = $("td.ms-vb-title > div.ms-vb", $(this)).attr("id");      
var viewLink = $("<td class='ms-vb-icon'><IMG style='CURSOR:hand; BORDER-RIGHT-WIDTH: 0px; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px' title='View Properties' alt=Search src='/_layouts/images/gosearch15.png' /></td>");            

$(this).append(viewLink);         
viewLink.click(
function(event){                 
var options = {        
url: "/MyLibrary/Forms/DispForm.aspx?ID=" + id,        
title: "Document Properties",        
allowMaximize: true,        
showClose: true,        
dialogReturnValueCallback: function(dialogResult, returnValue) { }       
};
             
SP.UI.ModalDialog.showModalDialog(options);      
}
);     
}   
});

This just adds an extra column to the end with an icon that opens the ‘View Properties’ dialog.

You can either use a Content Editor web part to make it view specific or add it to a global script for all views.
It’s a little hacky, but does the job.

It relies on the title column being available to extract the id of the item.

Comment and share

You may have the need to run some code in a SPServiceContext of a specific user. e.g. when dealing with user profiles or activities.

This can be achieved by using a combination of the SPUser, WindowsIdentity, GenericPrincipal and the GetContext method of SPServiceContext.

From these we can set the HttpContext.Current.User property to the specified user.

Don’t forget to set the context back after the code has executed!

Below is a helper method that makes this very easy.

I haven’t done any solid testing on performance etc. so the usual caveats apply.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

// Example calling code

RunAs("http://asharepointsite", "THEDOMAIN\THEUSER", c =>
{
// The code to execute. e.g. get a the UserProfileManager as the passed in user
var upm = new UserProfileManager(c, true);

// Do some stuff to the UPM as the passed in user
});

// Helper method

private static void RunAs(string siteUrl, string userName, Action<SPServiceContext> contextToUse)
{
try
{
var currentSetting = false;
var currentHttpContext = HttpContext.Current;
SPUser currentUser = null;

if (SPContext.Current != null)
{
if (SPContext.Current.Web != null)
{
currentUser = SPContext.Current.Web.CurrentUser;
}
}

SPSecurity.RunWithElevatedPrivileges(delegate
{
using (var site = new SPSite(siteUrl))
{
using (var web = site.OpenWeb())
{
try
{
var user = web.EnsureUser(userName);

currentSetting = web.AllowUnsafeUpdates;
web.AllowUnsafeUpdates = true;

var request = new HttpRequest("", web.Url, "");

HttpContext.Current = new HttpContext(request,
new HttpResponse(
new StringWriter(CultureInfo.CurrentCulture)));

HttpContext.Current.Items["HttpHandlerSPWeb"] = web;

var wi = WindowsIdentity.GetCurrent();

var newfield = typeof (WindowsIdentity).GetField("m_name",
BindingFlags.NonPublic |
BindingFlags.Instance);

if (newfield != null) newfield.SetValue(wi, user.LoginName);

if (wi != null) HttpContext.Current.User = new GenericPrincipal(wi, new string[0]);

var elevatedContext = SPServiceContext.GetContext(HttpContext.Current);

contextToUse(elevatedContext);

//Set the HTTPContext back to "normal"
HttpContext.Current = currentHttpContext;

if (currentUser != null)
{
var winId = WindowsIdentity.GetCurrent();

var oldField = typeof (WindowsIdentity).GetField("m_name",
BindingFlags.NonPublic |
BindingFlags.Instance);

if (oldField != null) oldField.SetValue(winId, currentUser.LoginName);

if (winId != null)
HttpContext.Current.User = new GenericPrincipal(winId, new string[0]);
}
}
catch (Exception ex)
{
// Log or whatever
}
finally
{
web.AllowUnsafeUpdates = currentSetting;
}
}
}
});

}
catch (Exception exO)
{
// Log or whatever
}
}

Comment and share

I haven’t seen a massive amount of documentation on MSDN or other posts about this, so I thought I would write a small note.

This details a way of extracting the info you see in the news feed on your ‘My Site’

First of all you need to get the the format used by the ActivityEvent, this done using ActivityType and ActivityTemplate.

1
2
3
4
5
ActivityType type = activityMan.ActivityTypes[activity.ActivityEventId];

ActivityTemplate template = type.ActivityTemplates[ActivityTemplatesCollection.CreateKey(false)];

string format = GetResourceString(template.TitleFormatLocStringResourceFile, template.TitleFormatLocStringName, (uint)CultureInfo.CurrentUICulture.LCID);

The format variable will now contain something along the lines of “{Pubisher} has posted a note {Link} on {PublishDate}”

Now you could at this point take the corresponding Properties in the ActivityEvent, but there is a whole load of formatting that needs to be done. The Publisher Tag needs to be turned into a link to the users profile page and display their name, etc, etc.

There is an easier way, you can use the static methods of the ActivityTemplateVariable.

1
ActivityTemplateVariable.DateOnlyToString(tag, variable, ContentType.Html, CultureInfo.CurrentCulture);

The problem here is that you have to pass in an ActivityTemplateVariable as one of the parameter and there is no obvious way of doing this.

There are no properties or methods in the ActivityEvent that give you an ActivityTemplateVariable or is there?

After further investigation of the TemplateVariable string property, you can see that this actually returns an XML string. This XML string is a de-serialised ActivityTemplateVariable object.

1
var variable = (ActivityTemplateVariable)FromXml(item.TemplateVariable, typeof(ActivityTemplateVariable));

You can now use this object to pass to the static method of ActivityTemplateVariable to return the full HTML representation of the tag.

If you then combine that with SimpleTemplateFormat you can then loop round all the tags and replace them.

1
var items = SimpleTemplateFormat.SimpleParse(formatToUse);

Comment and share

If anyone knows a better way then let me know.

1
2
3
4
5
6
7
8
9
10
11
string smtp = string.Empty;

SPFarm.Local.Servers.ForEach(s => s.ServiceInstances.ForEach(i =>
{
if(i is SPOutboundMailServiceInstance)
{
smtp = s.Address;
}
}));

return smtp;

I use a ForEach extension which I’ve included below

1
2
3
4
5
6
7
public static void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
foreach(T item in enumerable)
{
action(item);
}
}

Comment and share

For a standard team site (STS#0) the GlobalNodes SPNavigationNodesCollection will contain three SPNavigationNode objects.

Home, Quick Launch and SharePoint Top Navigation Bar, these each have Ids of 1000, 1002 and 1025 respectively.

The problem occurs if you delete Quick Launch or SharePoint Top Navigation nodes. Even if you then add them back in a new ID is generated and SPWeb.Navigation.TopNavigationBar looks for child nodes under a node with an Id of 1002 (This is hard coded).

Through the API this means that SPWeb.Navigation.TopNavigationBar will always be null and through the SharePoint UI an error will be returned to the user (“Value does not exist in that range”).

The only ways I have found to fix this is to delete and re-create the site or (Microsoft please close your ears) get the site id and web id and query the NavNodes table in the content database the site collection sits in and add the following records

SiteId WebId Eid EidParent NumChildren RankChild ElementType Url DocId Name DateLastModified NodeMetaInfo NonNavPage NavSequence ChildOfSequence
C803FD13-27FA-475F-909C-2CE5B2048E1B A31E8093-1505-4E1C-B7CC-04D6B952A33F 1000 0 0 0 0 NULL A31414EE-953B-44CC-A37E-4D5D643B002A Home 2008-04-04 11:28:21.000 NULL False False False
C803FD13-27FA-475F-909C-2CE5B2048E1B A31E8093-1505-4E1C-B7CC-04D6B952A33F 1002 0 0 2 1 NULL NULL SharePoint Top Navigation 2008-04-04 11:28:21.000 NULL False True False
C803FD13-27FA-475F-909C-2CE5B2048E1B A31E8093-1505-4E1C-B7CC-04D6B952A33F 1025 0 0 1 1 NULL NULL Quick Launch 2008-04-04 11:28:21.000 NULL True True False

Comment and share

I have been trying to rebuild a development environment recently using DBA created tables.

Every time I ran the PSCONFIG -cmd configdb -create command it ran and then threw an error.

The error message above was logged.

After looking through the tables, I noticed that some of the tables in both the config db and the config admin db had been created under the user account I had been running the installation under and some had been created under dbo.

There are a couple of SQL scripts in the layouts folder under SQL that are run to create the schemas and here I think is the problem.

Most of the CREATE TABLE statements using the following syntax CREATE TABLE [dbo].[TableName], but there are a few tables in both databases that just use CREATE TABLE TableName.

The error occurs because further SQL script is then referencing [dbo].[TableName], but these tables don’t exist under the dbo user schema, so it errors.

The solution is to change the SQL scripts so all CREATE TABLE statements have [dbo]. infront of them. Before you run it again you will need to clear out all the objects from both the databases.

We have some rather quirky database rights in our environment, but the install account and the farm service account both had dbcreator and secuirtyadmin rights and were in the db_owner role for each of the databases.

Comment and share

To match the ScopeId in the EventData property of the SPAuditEntry to a SPListItem you need to use the hidden column ScopeId and not the Id property of SPListItem.

Here is what I mean.

The following XML is returned in the EventData proprty for a SPAuditEventType of SecRoleBindUpdate. To match the Scope returned in the XML to SPSite, SPWeb and SPList object you would use the Id property of this object, but not for the SPListItem.

1
2
3
<roleid>1073741826</roleid>
<principalid>11</principalid>
<scope>72EEC412-B14B-4EFB-AB95-EA821A3A4C63</scope>

You need to use the ScopeId column of the SPListItem

So why can’t I just use the SPAuditQuery.RestrictToListItem method to return the audit entries for that SPListItem.

It doesn’t work, that’s why and this has been confirmed by MS Support.

Here is a workaround that will link the ScopeId to the SPListItem that triggered the audit entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
using System; 
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace ConsoleApplication1
{  
class Program  
{  
static void Main(string[] args)  
{
string siteUrl = "http://url";  
string listName = "TheList";  
string scopeIdFromItem = string.Empty;  
string scopeIdFromChange = string.Empty;

try  
{  
using (SPSite site = new SPSite(siteUrl))  
{  
using (SPWeb web = site.OpenWeb())  
{
SPList list = web.Lists[listName];
SPRegionalSettings regionalSettings = web.RegionalSettings;  
SPTimeZone timeZone = regionalSettings.TimeZone;
SPAuditQuery query = new SPAuditQuery(site);  
SPAuditEntryCollection auditEntries = site.Audit.GetEntries(query);

foreach (SPAuditEntry auditEntry in auditEntries)  
{  
if (IsSecurityEvent(auditEntry))  
{  
try  
{  
if (auditEntry.Event.ToString().ToUpper() == "SECROLEBINDUPDATE")  
{
// Should really do a proper XML query here, but for demo just do some string stuff
scopeIdFromChange = auditEntry.EventData.ToString().Substring((auditEntry.EventData.ToString().Length - 44), 36);
foreach (SPListItem item in list.Items)  
{ // Square brackets round the scope id need to be removed
scopeIdFromItem = item["ScopeId"].ToString().Substring(1, 36);

if (scopeIdFromChange == scopeIdFromItem)  
{  
Console.Write("Event Data : ");
Console.WriteLine(auditEntry.EventData.ToString());  
Console.Write("Occurred : ");
Console.WriteLine(timeZone.UTCToLocalTime(auditEntry.Occurred));

if (list.BaseType == SPBaseType.DocumentLibrary)  
{  
Console.WriteLine("Security changed on file: " + item.File.ToString());  
}  

if (list.BaseType == SPBaseType.GenericList)  
{  
Console.WriteLine("Security changed on item: " + item.Name.ToString());  
}  
}  
else  
{  
Console.WriteLine("No item found in list " + siteUrl + "/" + listName + " for " + auditEntry.EventData.ToString());  
}  
}  
}  
}  
catch (Exception ex)  
{  
Console.WriteLine(ex.Message.ToString());  
}
}  
}  
}  
}  
}  
catch (Exception ex)
{  
Console.WriteLine(ex.Message);  
}  
}

private static bool IsSecurityEvent(SPAuditEntry entryToTest)  
{  
SPAuditEventType[] SECURITY_EVENTS = new SPAuditEventType[]  
{  
SPAuditEventType.SecGroupCreate,  
SPAuditEventType.SecGroupDelete,  
SPAuditEventType.SecGroupMemberAdd,  
SPAuditEventType.SecGroupMemberDel,  
SPAuditEventType.SecRoleBindBreakInherit,  
SPAuditEventType.SecRoleBindInherit,  
SPAuditEventType.SecRoleBindUpdate,  
SPAuditEventType.SecRoleDefBreakInherit,  
SPAuditEventType.SecRoleDefCreate,  
SPAuditEventType.SecRoleDefDelete,  
SPAuditEventType.SecRoleDefModify  
};

foreach (SPAuditEventType eventType in SECURITY_EVENTS)  
{  
if (eventType == entryToTest.Event)
return true;  
}
return false;
}
}
}
}
}

Comment and share

We were getting an error on one of our Load balanced web servers.

First of all we got “Attempted to read write to protected memory” error followed by the “The path specified cannot be used at this time” which is then logged every minute in the event viewer on our server.

This then ends up using all the resources on that server, which then makes the server become unavailable. Once the machine becomes unavailable it will move onto the next server and kill that one.

This was reported to Microsoft support and they identified it as a bug that will be fixed in SP1.

In the meantime the following will need to be done to work around this problem.

  1. Install the following hotfix 923028
  2. Retype the password for the search service from The central administrator page.
  3. Run “services.msc” and select the Windows SharePoint Services Timer
  4. Make sure the service is running with a MOSS service account, if it is running with MOSS services account then stop the service, retype the password and then start the service account again.
  5. Install / script a tool that automatically restarts the Windows SharePoint Services Timer when the following Event IDs are raised; 6398, 6482 and 7076. Microsoft Support maybe able to provide you with this tool if you raise a support request with them.

This is a particularly scary bug to be appearing in a “Production” ready product, especially as it is only happening in the farm environment and can take down you whole farm.

I believe there needs to be more information on the whole way SharePoint propagates between servers in a farm, there seems to be little understanding of it presently.

Comment and share

Author's picture

Toby Statham

Independent Office 365 / SharePoint Specialist and an associate consultant at aiimi.com, an Information Management company.

Independent Office 365 / SharePoint Specialist

Brighton, UK