Friday 19 October 2012

I Deleted My Last Post About Dynamically Rendering Controls From a SharePoint List

because I was stupid enough to forget that it is impossible, without a lot of fuss and bother, to add event handing to controls which are only rendered at runtime. I'll just take my dunce hat and sit in the corner.

It was not a total waste of time, however, as I was able to use the looping code and Response.Write to write the markup to a text file and then stick it back into the ASPX page. But as they say when looking for directions in Ireland, my home country, "well I wouldn't start from here if I were you."

Sigh. The loop DOES work well when submitting to list and that's coming up shortly.

Monday 1 October 2012

Oi! My Content Editor Web Part is Borked!

Had an issue today where I got an email saying "ever had this error before?" It was a Content Editor Web Part (those thingies that allow you to add fancy text to a SharePoint page via a web part) that was causing the browser to throw errors. The browser was apparently failing to recognise the background colour or some other bit of javascript. Also the context menu that dropped down from the "edit" command on the web part - viewable when you select "Edit Page" - was not dropping down. The whole thing seemed borked.

I tried adding in a few other Content Editor Web Parts and for the most part they broke in the same way. I closed them using the Web Parts Maintenance Page (which is like the regular page only you add ?contents=1 to the URL) and then had a look at the original web part. Clicking Source Text Editor, I noticed the following.
<DIV blah blah blah piles of attributes that looked as if they'd been lifted out of MS Word etc etc>
<p>Text</p>
</DIV>

Since the DIV contained no text or formatting, I deleted it and refreshed the web part. Hey presto, the borkedness was fixed. The tags were interfering with the HTML. Interesting indeed if this is being caused by SharePoint's very own Rich Text Editor!

Thursday 27 September 2012

Help! I can't create a Site Collection without getting "Access Denied" errors!

Another one of those problems which is a short post but which took me hours upon hours to fix. The error is as follows:

You log into SharePoint Central Admin with the intent of creating a site collection. You go to Applications and do the necessary, ensuring it is added to the correct Web Application yada yada yada. And then you go to the new link for your site collection - because you want to create some sites in it, naturally enough.

Access Denied Error!

Butbutbut - I'm the Farm Administrator! How can this be? How can I not see my own damn site collections?

By the time I had found the solution, via the good offices of google, I was quite ready to be the Funny Farm Administrator. But never mind. I know now what it is.


1. Central Admin. Attempt to access SharedServices1 page. Chances are you will get an Access Denied error

2. Application Management - Policy for Web Application. Is your farm account listed there with Full Control? If not, add it for the relevant Web Application (you'll see the web app filter in the view dropdown on the top right)

3. Still having problem? Go to Operations - Service Accounts. BE VERY CAREFUL ABOUT DOING THIS ON A PRODUCTION SERVER, HAVE A BACKUP FIRST:
Click "Web application pool" option button
Select Web Service - Winds SharePoint Services Web Application
Select Application Pool - usually only Sharepoint - 80 available. That's fine.
Select "Configurable" radio button and enter farm admin account user and password
Click OK
A lot of the links in SharedServices will still be broken but you will be able to view SharedServices1 and more importantly your site collections!

Monday 13 August 2012

Impersonating a SharePoint search

Here is code which will run a SharePoint search and return the results in a StreamWriter object, which can be outputted to a file. Hook it up to a console app and off you go!
Note 
-  you must specify a row limit or it will only return the first 50 results
-  user under which it runs will need to have read access to the content database containing the Central Administration Shared Services. This is usually called SharedServices1_DB or some such variant. Error logs will indicate very clearly if that error occurs so make sure to have good error handling
- Microsoft.Office.Server.Search.Query is another one of those annoying DLLs that is impossible to find. It's not one of the SharePoint dlls - you might find it in the ISAPI folder or otherwise in the list of other .NET dlls.

using System.IO;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search;
public class SearchResults
    {
        public string errorArgs;
        public SearchResults()
        {
            //constructor don't do nuthin'
            errorArgs = string.Empty;
        }
        protected DataTable ReturnSearchResults(string searchCriterion, string siteUrl)
        {
            DataTable dt = new DataTable();
           
            try
            {
               //Available fields: Title,FileExtension,ContentType,Created,LastModifiedTime,IsDocument,owsStartDate,Path
                
                searchCriterion = searchCriterion.Replace(" ""+");
                string fullTextSQL = "SELECT Title, FileExtension, ContentClass, ContentType, Created, URL FROM SCOPE() ";
                fullTextSQL = fullTextSQL + "WHERE CONTAINS ('" + searchCriterion + "') ";
                //do whole site for the moment
                using (SPSite site = new SPSite(siteUrl))
                {
                    errorArgs = "accessing office search component";
                    Microsoft.Office.Server.Search.Query.FullTextSqlQuery search = newMicrosoft.Office.Server.Search.Query.FullTextSqlQuery(site);
                    Microsoft.Office.Server.Search.Query.ResultTableCollection results;
                    //configure the query - limit it to 50 for the moment
                    errorArgs = "preparing search";
                    search.QueryText = fullTextSQL;
                    search.ResultTypes = Microsoft.Office.Server.Search.Query.ResultType.RelevantResults;
                    search.RowLimit = 1000;
                    search.TrimDuplicates = true;
                    results = search.Execute();
                    errorArgs = "search successful";
                    if (results.Count > 0)
                    {
                        using (Microsoft.Office.Server.Search.Query.ResultTable relevantResults = results[Microsoft.Office.Server.Search.Query.ResultType.RelevantResults])
                        { dt.Load(relevantResults, LoadOption.OverwriteChanges); }
                    }
                    return dt;
                }
            }
            catch (Exception ex)
            {
               //handle error here
                return null;
            }
            finally
            {
            }
        }
        public StreamWriter ReturnResultsInStream(string searchCriterion, string siteUrl,FileStream fs)
        {
            try
            {
                StreamWriter stream = new StreamWriter(fs);
                DataTable results = this.ReturnSearchResults(searchCriterion, siteUrl);
                foreach (DataRow dr in results.Rows)
                {
                    foreach (object field in dr.ItemArray)
                    {
                        if (field is string)
                        {
                            stream.Write(field.ToString());
                            stream.Write(";");
                        }
                    }
                    stream.WriteLine();
                }
                return stream;
            }
            catch(Exception ex)
            {
                //HANDLE YOUR ERROR HERE
                return null;
            }
        }
    }

Saturday 16 June 2012

An Addendum to the Last Post: Only Works on Document Libraries

I should have noted this at the time, but I was not aware of it. It was only when I tried to do the same thing with an SPList object that I ran into trouble.

This is why - BeforeProperties and AfterProperties do not work on a list

So I ended up having to create a Just Updated flag for the user to click when they want to send an email. Just before ending the event handler, it sets that flag back to false. Yet another hack to make up for SharePoint 2007's "by design behaviour" (sigh)

So if using the code in the previous post - document library only, lads. Sorry.

Saturday 19 May 2012

How to use BeforeProperties and AfterProperties in an RunWithElevatedPrivileges block

I came up against a tricky problem this week when I wrote code that would not work for a non-admin account and needed to wrap it up in a privilege block (note: I am not using RunWithElevatedPrivileges as it's dodgy, see earlier blog entries on the subject, but it's along similar lines.)

As explained before, an override method for an SPItemEventReceiver object always passes in the properties of the list item as a parameter. This allows for manipulation of the object. 

But we also know that the properties object cannot be used within the SPSecurity.RunWithElevatedPrivileges block as an entirely new site object needs to be created with separate web, list and item objects created with it. This is fine usually, but what if we need to compare the value of a column before being updated with the results after it has been changed? Usually we would be able to avail of properties.BeforeProperties["Fieldname"] and properties.AfterProperties["Fieldname"] and compare the two, but our new SPListItem object inside the privilege block does not possess either of these attributes. So, what to do?

The solution is below - important lines in bold:

SPItemEventDataCollection before = properties.BeforeProperties;
SPItemEventDataCollection after = properties.AfterProperties;
Guid siteID = properties.SiteId;
Guid webID = properties.ListItem.Web.ID;               
//this is for getting the user Token to log in as system
SPSite tempSite = new SPSite(siteID);
SPUserToken sysToken = tempSite.SystemAccount.UserToken;
//need to dump this as not declared in a using block
tempSite.Dispose();
//start the privilege block
using (SPSite site = new SPSite(siteID, sysToken))
{
    using (SPWeb web = site.OpenWeb(webID))
    {
        if (before["FieldName"].ToString() != after["FieldName"].ToString())
        { someBooleanVariable = true; }
        //do work
    }

}

Thursday 26 April 2012

Argh! I Locked Myself Out!

But I didn't get back in by being given a key - more like kicking the door down, but anyway...

A few days ago I was testing something on the dev machine and removed a few user groups from the site permissions list on a site collection. They were still associated with the site, just had no site permissions. I was logged in as site admin. Went to do something on one of those sites and what do you know, I can't get in! Checked the site collection admin, where I was queen of everything and all looked well. Checked Central Admin and the Sharepoint:80 web application, found my errant site collection, and yes I was site admin. Still couldn't get in.

Here is how I got back in. I do not recommend using this as a method to get in. It is a horrible hack and will consign me to SharePoint Hell. I only did it because it was a dev machine and the outcome was not important. However - it is an example of how to dynamically add an existing SharePoint user group onto a Site Permissions list if it isn't on it already:


using (SPSite site = new SPSite("http://notmyemployerssitecoll/sc/sample/"))
{
     using (SPWeb web = site.OpenWeb())
     {
         foreach (SPGroup group in web.AssociatedGroups)
         {
             if (group.Name.Contains("My Little Lost Group"))
             {
                 //assign it permissions to control everything
                 //obviously when we do this for real, we want it to grab the
                 //existing role assignment
                 SPRoleAssignment roleAssignment = newSPRoleAssignment(web.SiteGroups[group.Name]);
                 SPRoleDefinitionBindingCollection roleDefinition = roleAssignment.RoleDefinitionBindings;
                 roleDefinition.Add(web.RoleDefinitions["Full Control"]);
                 web.RoleAssignments.Add(roleAssignment);
                 web.Properties[group.Name] = "Full Control";
                 web.Properties.Update();
             }
         }
     }
}

Wednesday 18 April 2012

SPUtility.RunWithElevatedPrivileges Fails with getting SPUser from groups

Using SPUtility.RunWithElevatedPrivileges fails when trying to get SPUser objects from groups. It worked when I removed the privilege block, but this was not practical for non-admin users. The code below worked perfectly for me:


            Guid groupWebID = SPContext.Current.Web.ID;
            Guid groupSiteID = SPContext.Current.Site.ID;
            SPUserToken sysToken = SPContext.Current.Site.SystemAccount.UserToken;
 
            using (SPSite groupSite = new SPSite(groupSiteID, sysToken))
            {
                using (SPWeb groupWeb = groupSite.OpenWeb(groupWebID))
                {
 
                    SPGroup group = groupWeb.Groups["My Group"];
                    foreach (SPUser user in group.Users)
                    {
                        AllUsers.Items.Add(user.Email);
                    }
 
                }
            }

Sunday 15 April 2012

Not SP-Related: Searching for a Proper Name Within Range of a Word

This is not related to the day job or to sharepoint but it is coding stuff so here is as good a place as any to put it. I rarely do geek stuff in my free time but I thought this might be of some interest.

I was having a discussion with a very nice journalist on the tweet machine a few weeks ago and proposed I write some code for her. She wanted to search a Very Large CSV file (which I won't name cos it's a bit political and this blog is not about politics :-) about 2GB in size for names surrounding various words. One of these words was the word "whistleblower".

I came up with an algorithm that would search either a 50-character radius either side of the instance of the word, or, if there weren't enough characters, the whole line (I was asking the filestream to ReadLine() each time) It would look for a word that had a capital letter that was not preceded by a space and a full stop. In order to make it run, I downloaded the freebie edition of Microsoft Visual Studio C# Express, which had everything I needed for the purpose.

Now this is not perfect and it needs quite a bit of work. For a start, I need to write some extra code to grab the initial code of each cable (yeah, it's becoming more obvious what I'm referring to here, for the record allow me to say that I have no interest in the cause this organisation espouses, or affection for its leaders, but given it is now in the public domain...::shrugs::) But if I wait till I do that, I might never get it done. Also the file has the annoying habit of containing a lot of acronyms and randomly capitalised words, a prob which I can't get around at present.

What this code does: Examines file and searches for every instance of the word "whistleblower" (word can be changed) then writes everything to a file called results.txt, which will be found in the \bin folder of your C# project.

What you need to do. Download VS, Create a console application, go to Program.cs and copy this in wholesale. Sorry about the brackets, it should format them properly for you once it's pasted in

Oh and you need to download the source file. There are plenty of web resources telling you how to do that :)


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;

namespace ReadFromFile
{
class Program
{
static void Main(string[] args)
{
StreamReader reader = null;

//write to file - change YourFolder to your actual folder!
FileStream fs = new FileStream("C:\\YourFolder\\results.txt", FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
TextWriter tmp = Console.Out;

try
{
//read from file - change YourFolder to your actual folder!
string fileName = "C:\\YourFolder\\cables.csv";
reader = File.OpenText(fileName);
Console.SetOut(sw);

while (!reader.EndOfStream)
{
string line = reader.ReadLine();
int position = -1;
//change to lower case version of word
position = line.IndexOf("whistleblower");
if (position == -1)
{
//change to capitalised version of word
position = line.IndexOf("Whistleblower");
if (position == -1)
{
//no instances, next row
continue;
}
}

//we have a value for required string
string subLine = line;
if ((position - 50) >= 0 && ((position + 50) <= line.Length))
{
subLine = line.Substring(position - 50, position + 50);
}
else
{
subLine = line.Substring(0, line.Length);
}

char[] characters = line.ToCharArray();

for (int i = 0; i < characters.Length; i++)
{
if (Char.IsLetter(characters[i]) && (i >= 2))
{

if ((!characters[i - 2].Equals(".")))
{

if (Char.IsUpper(characters[i]))
{ //do stuff
//start from i and go to next space
Console.Write("Found possible name string: ");
for (int j = i; j < characters.Length; j++)
{
if (!Char.IsWhiteSpace(characters[j]))
{ Console.Write(characters[j]); }
else
{ Console.WriteLine(); break; }
}
}
}
}

}
}

}
finally
{
//close in and out files
sw.Close();
fs.Close();
reader.Close();
}

//reset console output
Console.SetOut(tmp);
}
}
}