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);
}
}
}


Wednesday 11 April 2012

Deleting Items on a List using SPWeb.ProcessBatchData()

I had a situation a week ago where I needed to write a script to delete all items in a list while maintaining the list structure. Now for those familiar with the SharePoint API, the obvious solution would be something like this:

foreach (SPListItem item in list.Items)
{
    item.Delete();
}


However theIEnumerator interface does not like deleting things while iterating through them because the list count will change while you're doing it. This is mighty confusing for an iterating loop so that's handled as an error and SharePoint sez, no can do, sorry. I tried using list.Items[0].Delete() as I thought that might work since there will always be at least one item in the collection, but no - when it got to the end of the collection it threw an error and all my old VB programming days of On Error Resume Next could not help me in my attempts to keep the code running after the error.


But anyway. All this has happily become a moot point since discovering the SPWeb.ProcessBatchData() command, since all I have to do is loop through every list in the web and issue a delete command. At first I got to the Microsoft help for this (always, always a bad idea. Sorry, Microsoft, but there it is) and got scared off by talk of OWS files and piles of XML. How and ever, after a bit of digging I found a great entry on Stack Overflow that assures me one does not need to be rooting around obscure XML files in the Twelve Hive in ungraceful fashion, but can build the XML on the fly as a string, feed it in to the ProcessBatchData command and hey presto, you're off:



foreach (SPList list in web.Lists)
   {
     try
       {
        //decrement loop so as not to confuse the iEnumerator interface
       Console.WriteLine("Deleting list items in following list : " + list.Title);
       SPListItemCollection splic = list.Items;
       StringBuilder batchString = new StringBuilder();
       batchString.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");
       foreach (SPListItem item in splic)
       {
         batchString.Append("<Method>");
         batchString.Append("<SetList Scope=\"Request\">" + Convert.ToString(item.ParentList.ID) + "</SetList>");
         batchString.Append("<SetVar Name=\"ID\">" + Convert.ToString(item.ID) + "</SetVar>");
         batchString.Append("<SetVar Name=\"Cmd\">Delete</SetVar>");
         batchString.Append("</Method>");
       }
       batchString.Append("</Batch>");
       web.ProcessBatchData(batchString.ToString());
    }
    catch { //whatever }

 }





Please note that I actually found this code somewhere on Stack Overflow, but have gone and lost the link. So Unknown Programmer, you have my greatest gratitude for this!

I believe there are Copy and Insert methods also but will have to check those out. I may have a requirement for these, due to SharePoint UI inflexibility, so stay tuned...