SharePoint Language specific CSS loading

Today I worked on a annoying problem where I had to figure out how to do a language specific CSS file loading during header load from a Master Page.

The solution is as following in our case:

Our code deploys under the LAYOUTS folder our project style sheets, js files etc. We created for each language ID it’s own folder and under each language folder the css file that we wanted to use for a each language.

Next we addedthe following line inside the <HEAD> element and the right CSS file based on the language ID was loaded:

<SharePoint:CssRegistration name=”<% $SPUrl:/_layouts/projectname/styles/~language/master.css %>” runat=”server”/>

In the definition above what is in bold and in blue is what you MUST add for the reference to work. What is in green and not in bold is your path where your CSS file is located. Change it to fit your needs.

What is the most interesting part is the “~language” definition in the URL. This will retrieve the proper language ID(example for English-US: en-US). The final URL reference will look something like this:

/_layouts/projectname/styles/en-US/master.css

Advertisements

C# .NET – Displaying debug information in Debug compilation

If you want to output extra infromation in your code to see what is going on in problem situations and you only want to include it in debug compilations and not in Release version you can add a “Debug” Conditional attribute to a function and use that function to output data into your system output. There are many ways but this is a simple and straightforward way to do it:

[Conditional(“DEBUG”)]
private void DebugInfo(String info)
{
#if DEBUG
Response.Write(“</br>” + info);
#endif
}

The function and it’s content will be left out in Release compilation.

How to deal with a single long continuous word in CSS and HTML

If you have a very long piece of text that has no white spaces or anything that would make the word into smaller bits or information this will likely cause problem when rendered by the browser. Might cause your content to explode width wise.

In one on my problem cases I was creating a custom XSLT to display email related data. One of the data was the To information of emails which holds the recipients of an email. If the email had lots of recipient they where one after another in a single continuous string. This caused a problem when the data was retrieved from the CMS system and displayed to the user. Width wise everything exploded.

What we did in our case the had a table into which the data was inserted. The TD element was modified so that the content would be scroll able and forced to a certain width. This may help someone if you have a similar problem.

<td style=”WIDTH: OVERFLOW: hidden” class=”ms-vb”>
<span style=”DISPLAY: block;WIDTH: 257px; OVERFLOW: auto”>
<xsl:value-of select=”fieldname”/>
</span>
</td>

Hiding Custom SharePoint fields from edit mode, new window or display mode

A quick post:

Lets say that you do not want to allow users to possibility to edit and insert values to a custom field in one of your lists. To do this you can set the values of one of the following properties of a SPField object:

  1. ShowInEditForm
  2. ShowInDisplayForm
  3. ShowInNewForm

You can do this programmatically at any time, below is a link on how to do it:

http://www.sharepointdynamics.net/2011/09/how-to-hide-a-sharepoint-list-column-from-a-list-form-new-edit-and-display/


#Get the web and site column objects
$web = Get-SPWeb http://portal
$column = $web.Fields["Test Column"]

#Set the PushChangesToLists property for the changes to be applied
#to lists where the column has already been added
$column.PushChangesToLists = $true

#Change the ShowInEditForm and other properties and update objects
$column.ShowInEditForm = $false
$column.ShowInNewForm = $false
$column.ShowInDisplayForm = $true
$column.ShowInViewForms = $true

$column.Update()
$web.Update()
$web.Dispose()

Remember to check that the change is applied to the list field also. If not then do the change directly to the list field.

 

Or by XML definition:

<Field Type=”Number” ShowInEditForm=”FALSE” ShowInNewForm=”FALSE” DisplayName=”Counter” Required=”FALSE” EnforceUniqueValues=”FALSE” Indexed=”FALSE” Group=”My Group” ID=”{7f111eff-569d-4852 8bf2-ba0fcee6c69a}” SourceID=”{b1498d8e-4ad9-4d0d-8584-53d9a5de8c11}” StaticName=”Counter” Name=”Counter”>
<Default>0</Default>
</Field>

Links to MSDN about the properties:

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfield.showineditform.aspx

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfield.showindisplayform.aspx

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfield.showinnewform.aspx

SharePoint Modified field gone crazy or it just won’t work :)

The problem is very simple in this case. The modified field does not work anymore. When a document or any list item is modified the time stamp does not get updated as it should.

To fix this problem you need to do two things unfortunately.

  1. First you need to update the out of the box Modified field. You can do this by a manually modified URL to the edit page of fields. You have to do this because the field is visible by default. Below is a sample URL:

http://vsp2007dev:24327/_layouts/fldedit.aspx?field=Modified

Pay attention to the bold part of the URL.Just past the bold part after your application domain name and you will be fine. Next all you need is to simply press the “OK” button and be sure to leave everything else unchanged. Especially the update option must be set to yes. What this will do is simply go through every “Modified” field use in your SharePoint application and makes some updates to the field. This will fix the time stamp problem.

2. After you do the seconds step you will run into another problem that an xml attribute from the Modified field schema is missing. This needs to be fixed by a script. Below you will find the code that fixes this error AND also does the update procedure in step 1 described above. So no manual labor here :).

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.SharePoint;

using System.Xml;

using System.IO;

 

namespace ModifiedFieldFix

{

    public class Program

    {

        static void Main(string[] args)

        {

            StringBuilder output = new StringBuilder();

            TextWriter tw = new StreamWriter(“LOGFILE_” + DateTime.Now.ToFileTime()  + “.txt”);

            if (args != null && args.Length > 0)

            {

                tw.WriteLine(“Given param: ” + args[0]);

                try

                {

                    using (SPSite msite = new SPSite(args[0]))

                    {

                        tw.WriteLine(“Site opened”);

                        SPField siteMField = msite.RootWeb.Fields[new Guid(“28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f”)];

                        siteMField.Update(true);

                        tw.WriteLine(“Modified field update request performed”);

                        foreach (SPWeb mweb in msite.AllWebs)

                        {

                            tw.WriteLine(“———————————————————————-“);

                            tw.WriteLine(String.Empty);

                            tw.WriteLine(“WEB UNDER PROCESSING: ” + mweb.Title);

                            tw.WriteLine(“WEB URL: ” + mweb.Url);

                            for (int i = 0; i < mweb.Lists.Count; i++)

                            {

                                try

                                {

                                    SPList mlist = mweb.Lists[i];

                                    SPField mfield = mlist.Fields[new Guid(“28cf69c5-fa48-462a-b5cd-27b6f9d2bd5f”)];

                                    tw.WriteLine(String.Empty);

                                    tw.WriteLine(“START OF LIST PROCESSING”);

                                    tw.WriteLine(“######################################################”);

                                    tw.WriteLine(“List Title: ” + mlist.Title + ” Field Title: ” + mfield.Title + ” FormBaseType ” + mfield.FromBaseType);

                                    tw.WriteLine(“List location:” + mweb.Url + “/” + mlist.RootFolder.Url);

 

                                    if (mfield.FromBaseType != true)

                                    {

                                        XmlTextReader xmlReader = new XmlTextReader(new StringReader(mfield.SchemaXml));

 

                                        XmlDocument xmlDocument = new XmlDocument();

 

                                        XmlNode xmlNode = xmlDocument.ReadNode(xmlReader);

 

                                        //xmlNode.Attributes[“Hidden”].Value = “FALSE”;

 

                                        XmlAttribute fromBaseType = xmlDocument.CreateAttribute(“FromBaseType”);

 

                                        fromBaseType.Value = “TRUE”;

 

                                        xmlNode.Attributes.Append(fromBaseType);

 

                                        tw.WriteLine(xmlNode.OuterXml);

 

                                        mfield.SchemaXml = xmlNode.OuterXml;

 

                                        mfield.Update();

                                    }

                                    tw.WriteLine(“######################################################”);

                                    tw.WriteLine(“END OF LIST PROCESSING”);

                                    tw.WriteLine(String.Empty);

                                }

                                catch (Exception ex)

                                {

                                    tw.WriteLine(“Error in list procesing of the MODIFIED FIELD FIX!!”);

                                    tw.WriteLine(ex.Message);

                                    tw.WriteLine(ex.StackTrace);

                                }

                                finally

                                {

                                    if (mweb != null)

                                        mweb.Dispose();

                                }

                            }

                            tw.WriteLine(“———————————————————————-“);

                            tw.Flush();

                        }

 

                    }

                }

                catch (Exception ex)

                {

                    tw.WriteLine(ex.Message);

                    tw.WriteLine(ex.StackTrace);

                }

                tw.WriteLine(output.ToString());

                tw.WriteLine(“END OF PROGRAM”);

                tw.Flush();

                tw.Close();

                Console.Write(output.ToString());

            }

        }

    }

}

SharePoint PowerShell access and command execution problems

I ran into a problem some time ago and only today I found a solution to this problem.

The problem was when I tried to run a powershell script as a user who is not an SharePoint admin. In other words the problem is related to not having sufficient privileges to run scripts against SharePoint in powershell.

The problem can be solved by using the following command:

Get-SPDatabase | Add-SPShellAdmin SomeDomain\SomeUserName

Lost files in Sharepoint Recycle Bin that can not be restored

This time I am writing a post about a problem that a client of mine had.

The problem is rather simple BUT required allot of time and energy to fix the problem or rather in my case to recover the items:

The client at some point by someone deleted files which ended up in the recycle bin. Later the client wanted to restore these files BUT was unable to. Sharepoint was telling that the restore operation failed even though you where able to clearly see that the files where available to restore in the Recycle bin of the site.

NOTICE: Before you start to try any of the things written in this post take notice TO NOT DO these things in you client product/live environment. If you chose to do so you will cause your client to lose their MS support. MS does not support manipulation of the content databases directly to SQL Server. So take a database backup, restore it in a test or development environment and fix you problem there.

NOTICE: Use this code at your own risk. The code does some crazy stuff directly to SharePoint content database. I will not take any responsibility on anything that you do with the application and/or any part of it and it’s code.

Here are the steps which I ended up taking and a more detailed explanation on them:

  1. Trying to restore the items through the object model
  2. Investigating the Sharepoint content database
  3. Trying to restore the files by fixing content database Recycle Bin table missing information in the files
  4. Trying to recover the files directly from the AllDocsStream table

Lets start from the first item and advance from there:

1. Trying to restore the items through the object model

This is basically the same as the UI BUT I decided to give it a quick try since nothing from the UI worked and sometimes you can do things through code that you can not do through the UI.

Long story short the object model did not offer much help and any restore operation failed again or nothing happened. For this a created a forms application(code is included at the end of this post).

Here is how the final version of the application looks like: appppp

In this first step I create the functionality in the “Blue” area. These where the object model fixes which did not help.

There basic functionality are provided which are in the “Blue” area:

  1. Restore the selected items from recycle bin
  2. Delete selected items
  3. And move them to the Site Collection Recycle Bin

2. Investigating the Sharepoint content database

At this point because the object model and the UI did not do what they are supposed to I simply gave up on using the “preferred” way of Microsoft and went directly into the content database. My first destination was the “Recycle Bin” table.

After a few moments of careful examination of the clients content database table I noticed that the documents that can not be restored have 4 table fields which are null while other data row in the table all have data in them.

The fields which where null where:

  1. ListId
  2. ListItemId
  3. AuthorId
  4. ListDirName

So I decided to create a code that would restore the missing values. I decided that the AuthorId and ListItemId would be defined by me during the code(since they could be anything any existing user and any integer number that is large enough not to conflict with existing ID enumeration in the target list) and the ListId and ListDirName are to be retrieved through the object model. This had to be done by retrieving the values of the following fields WebId, SiteId and DirName. In the code used these values to get the site, web and the list. After I got the list it was easy to just update the table with the list ID and the rootfolder ServerRelative URL into the ListDirName field.

You can find the code for this in the button named: “Repair Recycle Bin Items(DB)”

3. Trying to restore the files by fixing content database Recycle Bin table missing information in the files

Unfortunately what I did in step two was not enough because the sites and lists did not exist anymore for these deleted files that needed to be restored from the bin.

The next step was to try to update the following tables so that the document/file would be restored to a different web and list that actually exists.

I had to make modifications to the following tables:

  1. AllDocs = Data regarding the file/document itself, where it is, it’s parent, state etc
  2. AllDocStreams = The actual binary data of the file
  3. RecycleBin = The data of a deleted file that has ended up in the recycle bin

By retrieving the DocId value from RecycleBin table from a file that can not be restored you can correlate to the documents information in the AllDocs and AllDocStream.

This step is very complex and I due to time constraints I did not get it to work properly. Some piece of data was missing to get the file restored to another location. I managed to get the file restored BUT while the list showed that it had files in it you could not access the file. So something was missing from somewhere.

You can check the code for this in the button: “Repair Recycle Bin items(DB) to inserted URL”

4. Trying to recover the files directly from the AllDocsStream table

Finally the last step was to go directly into the AllDocsStream with ADO .NET, read the binary data, retrieve the documents real name from the RecycleBin table and write that data into the hard drive.

This was SUCCESSFUL!!! It worked like a charm and the client was happy to get their files restored.

In the AllDocsStrems table the most important field is the “Content” field. This is the actual binary data that you need to retrieve and use it to write it into your hard drive.

From the RecycleBin table you need the value from the LeafName field.

Using these two piece of information you can reconstruct the file.

Here is the code that solved my problem, you can find it behind the button: “Recover files from DB”

            

String connectionString = ConfigurationSettings.AppSettings[“DBConnectionString”];

            SqlDataReader rdr = null;

            this.textBox2.Text += “Opening connection to DB”;

            using (SqlConnection connection = new SqlConnection(connectionString))

            {

                connection.Open();

                this.textBox2.Text += “Connection opened”;

                List<RecycleBinDBItem> items = new List<RecycleBinDBItem>();

                try

                {

                    // Get all such items which can not be restored properly from the UI or by object model

                    SqlCommand cmd = new SqlCommand(“SELECT * FROM RecycleBin WHERE ListDirName IS NULL”, connection);

 

                    rdr = cmd.ExecuteReader();

                    this.textBox2.Text += “Select clause executed”;

 

                    while (rdr.Read())

                    {

                        this.textBox2.Text += “Reading line: \n”;

                        this.textBox2.Text += rdr[“DirName”].ToString();

                        items.Add(new RecycleBinDBItem(rdr[“DocId”].ToString(), rdr[“LeafName”].ToString()));

                    }

                }

                finally

                {

                    // close the reader

                    if (rdr != null)

                    {

                        rdr.Close();

                    }

                    int itemIDCounter = 5000;

                    int authorID = 1;

                    // Loop through every recycle bin items that is missing needed field data from the RecycleBin table to be restored properly

                    foreach (RecycleBinDBItem item in items)

                    {

                        try

                        {

                            SqlCommand cmd = new SqlCommand(String.Format(“SELECT Content FROM AllDocStreams WHERE Id = ‘{0}'”, item.docID ), connection);

 

                            // 2. Call Execute reader to get query results

                            byte [] bArray = (byte[])cmd.ExecuteScalar();

                            // Craete the file and write the binary data from the Database

                            FileStream fs = File.Create(item.fileName, (int)bArray.Length);

                            fs.Write(bArray, 0, bArray.Length);

                            fs.Flush();

                            fs.Close();

                        }

                        catch (SqlException sqlex)

                        {

                            this.textBox2.Text += sqlex.Message + ” #######\n” + sqlex.StackTrace;

                        }

                        catch (Exception ex)

                        {

                            this.textBox2.Text += ex.Message + ” #######\n” + ex.StackTrace;

                        }

                        finally

                        {

                            if (rdr != null)

                            {

                                rdr.Close();

                            }

                        }

                    }

                }

            }

            this.textBox2.Text += “DB operations done!”;

Final words;

Hope this might help someone else who is suffering from this problem. The application and it’s code is not the prettiest BUT I did what I could in the time that was given to me and also what the client is ready to spend to solve this problem. It’s only a tool and should be treated as such.

FINAL NOTICE: Use this code at your own risk. The code does some crazy stuff directly to SharePoint content database. I will not take any responsibility on anything that you do with the application and/or any part of it and it’s code.

Check out the CodePlex Project for the code:

https://recyclebinrestoreapp.codeplex.com/