Second Life of a Hungarian SharePoint Geek

March 26, 2017

Generating Pseudo GUIDs for Your Project Server Entities

Filed under: PowerShell, Project Server, Tips & Tricks — Tags: , , — Peter Holpar @ 06:24

As you might have known, since the version 2013, Project Server utilizes pseudo-GUIDs to improve Project Server performance. These ones has the format of a “classical” GUID, but actually generated sequentially. As Microsoft states in this TechNet article:

"We handle GUIDs a little better in Project Server 2013 – and in many places they are sequential GUIDs which cause less index fragmentation"

This topic is quite good described in the Project Conference 2014 presentation Project Worst Practice – Learning from other peoples mistakes by Brian Smith. See the video recording between 6:08-13:54, or the slides 10-14.

One of the main components of the pseudo-GUID generation is the NewSequentialUid method of the Microsoft.Office.Project.Server.Library.PSUtility class:

public static Guid NewSequentialUid() 

  Guid guid; 
  if (NativeMethods.UuidCreateSequential(out guid) != 0) 
    return Guid.NewGuid(); 
  byte[] b = guid.ToByteArray(); 
  Array.Reverse((Array) b, 0, 4); 
  Array.Reverse((Array) b, 4, 2); 
  Array.Reverse((Array) b, 6, 2); 
  return new Guid(b); 
}

So if you want to use the same kind of pseudo-GUIDs for your own custom entities you create from code, you can get the IDs by invoking the method (for example, via PowerShell). The code sample below illustrates, how to get a single ID, or a batch of  IDs (in this case, 5 of them):

# load the necessary assembly
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Project.Shared")
# generate a single sequential ID
[Microsoft.Office.Project.Server.Library.PSUtility]::NewSequentialUid()
# or generate a range of sequential IDs, in this case, five of them
(1..5) | % { [Microsoft.Office.Project.Server.Library.PSUtility]::NewSequentialUid().Guid }

October 1, 2015

The SharePoint Time Machine

Filed under: SP 2013, Tips & Tricks — Tags: , — Peter Holpar @ 22:06

Assume you have a SharePoint list with a lot of items. The list supports versioning and you should provide a snapshot of the items at a given time in the past.

As you know, the Versions property (of type SPListItemVersionCollection) of the SPListItem class contains the item versions. One can access a specific version via the indexer property of the collection, by the ID of the version (where the ID = 512 * major version number + minor version number), or by the version number (a.k.a. label, for example, 2.3), but there is no direct support to get the actual version at a specific time in the past.

To achieve my goal, I’ve implemented the GetVersionFromDate extension method, that iterates through the method, and returns the version we need based on its creation date:

  1. public static SPListItemVersion GetVersionFromDate(this SPListItemVersionCollection versions, DateTime localDate)
  2. {
  3.     SPListItemVersion result = null;
  4.  
  5.     if (versions != null)
  6.     {
  7.         DateTime date = versions.ListItem.Web.RegionalSettings.TimeZone.LocalTimeToUTC(localDate);
  8.  
  9.         SPListItemVersion prevVersion = null;
  10.  
  11.         // versions[0] – current item version
  12.         // versions[versions.Count – 1] – first item version created
  13.         for (int i = versions.Count – 1; i >= 0; i–)
  14.         {
  15.             SPListItemVersion version = versions[i];
  16.             if (version.Created > date)
  17.             {
  18.                 result = prevVersion;
  19.                 break;
  20.             }
  21.             // if it is the last (actual) version and there is no result yet,
  22.             // then the date specified should be greater than the creation date of the last version
  23.             // we take the last version
  24.             else if (i == 0)
  25.             {
  26.                 result = version;
  27.             }
  28.  
  29.             prevVersion = version;
  30.         }                
  31.  
  32.     }
  33.  
  34.     return result;
  35. }

Note, that the Created property stores the creation date as UTC time, that we should convert first.

Using this method accessing the specific version is so simple as:

  1. SPList list = web.Lists["Your List"];
  2. SPListItem item = list.Items.GetItemById(1);
  3.  
  4. DateTime date = DateTime.Parse("2015/06/29 13:40");
  5. SPListItemVersion version = item.Versions.GetVersionFromDate(date);
  6. Console.WriteLine(version["APropertyName"]);

If you go through the items in the list and get the version of the specific time, you already have the required snapshot.

February 9, 2015

“Decoding” SharePoint Error Messages using PowerShell

Filed under: PowerShell, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 22:26

When working with SharePoint errors in ULS logs, you can find the error message near to the stack trace. In case of simple methods the stack trace may be enough to identify the exact conditions under which the exception was thrown. However, if the method is complex, with a lot of conditions and branches, it is not always trivial to find the error source, as we don’t see the exception message itself, as it is stored in language-specific resource files, and you see only a kind of keyword in the code.

For example, let’s see the GetItemById method of the SPList object with this signature:

internal SPListItem GetItemById(string strId, int id, string strRootFolder, bool cacheRowsetAndId, string strViewFields, bool bDatesInUtc)

There is a condition near to the end of the method:

if (this.IsUserInformationList)
{
    throw new ArgumentException(SPResource.GetString("CannotFindUser", new object[0]));
}
throw new ArgumentException(SPResource.GetString("ItemGone", new object[0]));

How could we “decode” this keyword to the real error message? It is easy to achieve using PowerShell.

For example, to get the error message for the “ItemGone”:

[Microsoft.SharePoint.SPResource]::GetString("ItemGone")

is

Item does not exist. It may have been deleted by another user.

Note, that since the second parameter is an empty array, we can simply ignore it when invoking the static GetString method.

If you need the language specific error message (for example, the German one):

$ci = New-Object System.Globalization.CultureInfo("de-de")
[Microsoft.SharePoint.SPResource]::GetString($ci, "ItemGone")

it is

Das Element ist nicht vorhanden. Möglicherweise wurde es von einem anderen Benutzer gelöscht.

Having the error message, it is already obvious most of the time, at which line of code the exception was thrown.

It can also help to translate the localized message to the English one, and use it to look up a solution for the error on the Internet using your favorite search engine, as there are probably more results when you search for the English text.

August 31, 2014

How to use PowerShell to check if a SharePoint Group with a specified ID or name exists–Without error handling

Filed under: PowerShell, SP 2013, Tips & Tricks — Tags: , , — Peter Holpar @ 23:18

Recently I created a PowerShell script that should delete a group that has a specific name. If the script runs the second time, it throws an exception since the group is already deleted.

If you want to get/delete/add a group from/to a SPGroupCollection (like SiteGroups or Groups of an SPWeb) the methods throw typically exceptions of different kinds if the group does not exist (or already does exist in case of addition):

$web.SiteGroups.Remove(12345) throws
Specified argument was out of the range of valid values.

$web.SiteGroups.Remove("YourGroupName") throws
Group cannot be found.

$web.SiteGroups.GetByID(12345) and
$web.SiteGroups.GetByName("YourGroupName") throw
Group cannot be found.

$web.SiteGroups.Add("YourGroupName", $usr, $null, "Group description") throws
The specified name is already in use.

I wanted to eliminate the error messages. If these commands were PowerShell Cmdlets, we could use the common ErrorAction parameter with the value SilentlyContinue (see more here), however with standard .NET object calls only the Try/Catch block would be available.

Throwing and handling exceptions has always a performance penalty. How could we check if the group exists before trying to get/delete/add it from/to the collection?

After a short search on the .NET based samples I found:

  • A generic- and lambda expression-based solution, that is nice, but not easy to transfer to PowerShell.
  • An interesting solution, that uses the Xml property of the SPGroupCollection object .
  • A solution that is based on the GetCollection method of the SPGroupCollection object.

I chose the third sample to transfer to PowerShell. The equivalent PowerShell condition to check the group by ID:

@($web.SiteGroups.GetCollection(@(12345))).Count -eq 1

To check the group by name, we can use:

@($web.SiteGroups.GetCollection(@("YourGroupName"))).Count -eq 1

The parameter of the GetCollection method is an array, so we can use the same method to check if all or any of multiple groups exists.

For example, to check by ID if both of the groups we need exist:

@($web.SiteGroups.GetCollection(@(12345, 54321))).Count -eq 2

To check by name if any of the groups we need exists:

@($web.SiteGroups.GetCollection(@("YourGroupName1", "YourGroupName2"))).Count –gt 0

April 23, 2014

Who Has Deployed This Solution?

Filed under: PowerShell, Reflection, SP 2010, Tips & Tricks — Tags: , , , — Peter Holpar @ 22:09

Recently we had a problem in one of our server farms that was caused by a sandboxed solution that was deployed by mistake as farm solution as well. I wanted to know who has deployed the farm solution, but in the Central Administration we can see only the time of the deployment, but there is no information regarding the person who performed the action:

image

If we submit the following SQL query in the configuration database of the farm (using the name of the solution as the filter)…

  1. SELECT [Id]
  2.       ,[ClassId]
  3.       ,[ParentId]
  4.       ,[Name]
  5.       ,[Status]
  6.       ,[Version]
  7.       ,[Properties]
  8.   FROM [SharePoint_Config].[dbo].[Objects]
  9.   WHERE [Name] = 'addispname.wsp'

… we should receive two records as result that include XML objects in the Properties fields like these ones:

  1. <object type="Microsoft.SharePoint.Administration.SPPersistedFile, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
  2.     <sFld type="Int64" name="m_FileSize">4748</sFld>
  3.     <fld type="System.Collections.Hashtable, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" name="m_UpgradedPersistedFields" />
  4.     <fld name="m_Properties" type="null" />
  5.     <sFld type="String" name="m_LastUpdatedUser">CONTOSO\Administrator</sFld>
  6.     <sFld type="String" name="m_LastUpdatedProcess">vssphost4 (5876)</sFld>
  7.     <sFld type="String" name="m_LastUpdatedMachine">DEMO2010A</sFld>
  8.     <sFld type="DateTime" name="m_LastUpdatedTime">2013-08-22T00:01:04</sFld>
  9. </object>
  10. <object type="Microsoft.SharePoint.Administration.SPSolution, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
  11.     <sFld type="Guid" name="m_Id">31079555-a0db-4dce-8ca4-51e3e40cb6d1</sFld>
  12.     <sFld type="Boolean" name="m_WebPartPackage">False</sFld>
  13.     <sFld type="Guid" name="m_Wppid">00000000-0000-0000-0000-000000000000</sFld>
  14.     <fld type="Microsoft.SharePoint.Administration.SPServerRole, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" name="m_DeploymentServerType">WebFrontEnd</fld>
  15.     <sFld type="Boolean" name="m_HasWebAppResource">False</sFld>
  16.     <fld type="System.Collections.Hashtable, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" name="m_UpgradedPersistedFields" />
  17.     <fld name="m_Properties" type="null" />
  18.     <sFld type="String" name="m_LastUpdatedUser">CONTOSO\Administrator</sFld>
  19.     <sFld type="String" name="m_LastUpdatedProcess">vssphost4 (5876)</sFld>
  20.     <sFld type="String" name="m_LastUpdatedMachine">DEMO2010A</sFld>
  21.     <sFld type="DateTime" name="m_LastUpdatedTime">2013-08-22T00:01:04</sFld>
  22. </object>

As you can see, one of the objects (SPSolution) describes the solution itself, the other one (SPPeristedFile) describes the persisted file that belongs to the solution. From the properties of the persisted file we can read the information we need, see m_LastUpdatedUser.

If you don’t like the idea to query the SharePoint content database directly, there is an other way to achieve the same information, for example using PowerShell and a bit of Reflection.

First we get a reference to our solution either by ID (as long it is known):

$solution = Get-SPSolution -Identity ‘31079555-a0db-4dce-8ca4-51e3e40cb6d1’

or by Name:

$solution = Get-SPSolution | ? { $_.Name -eq ‘addispname.wsp’ }

We can get the persisted file of the solution via the SolutionFile property, then using Reflection we can read its internal LastUpdateInfo property that contains the information regarding the user that deployed the solution:

$persistedFile = $solution.SolutionFile
$persistedFileType = $persistedFile.GetType()
$bindingFlags = [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance
$pi_LastUpdateInfo = $persistedFileType.GetProperty(‘LastUpdateInfo’, [System.Reflection.BindingFlags]($bindingFlags))
$lastUpdateInfo = $pi_LastUpdateInfo.GetValue($persistedFile, $null)
Write-Host $lastUpdateInfo

We should have an output similar to this one:

User: CONTOSO\Administrator
Process:vssphost4 (5876)
Machine:DEMO2010A
Time:August 22, 2013 12:01:04.0000

Beyond the user, one can find other potentially interesting information as well, including the host the solution was deployed from, and the process used to deploy the solution (executable + process ID a.k.a. PID). In the sample above the solution was deployed from Visual Studio, but in other cases the value can be for example powershell (4736) that means it was deployed using PowerShell.

February 5, 2014

How to Display Web Analytics Reports for Site Collection without Visual Upgrade

Filed under: Analytics, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 00:01

Recently I had to display the web analytics reports by a customer of us in a SharePoint 2010 environment without the Visual Upgrade, that means, the site collection is still using the WSS 3.0 / MOSS 2007 design.

These kind of reports can be displayed via the Report.aspx page for a web site:

_layouts/WebAnalytics/Report.aspx?t=SummaryReport&l=s

as well as the site collection:

_layouts/WebAnalytics/Report.aspx?t=SummaryReport&l=sc

If we had the Visual Upgrade, these links would be included in the Site Settings under Site Actions:

image

However, in the case of the WSS 3.0 UI, the links are missing, and if we try to access the Report.aspx page we receive the warning below:

This operation requires that the new SharePoint user experience be active. A Site Owner or Administrator can enable the new experience from the Site Actions menu or from Site Settings option "Title, description, and appearance."

The warning comes from the OnPreRender method of the Microsoft.Office.Server.WebAnalytics.Reporting.WebAnalyticsReport  class (Microsoft.Office.Server.WebAnalytics.UI assembly), see the SPUtility.GetIncorrectUIVersionMessage(true) method call below:

    if ((contextWeb == null) || (contextWeb.UIVersion < 4))
    {
        if (this.IncorrectUIVersionMessageLabel != null)
        {
            this.IncorrectUIVersionMessageLabel.Text = SPHttpUtility.HtmlEncode(SPUtility.GetIncorrectUIVersionMessage(true));
        }
    }

From this code can we see, that only the UI version of the current web site (contextWeb in the code above) is checked. That is, if we created a sub site anywhere in the site collection (for example called reports under the root site) using the 2010 compatible UI, we can display the reports for the site collection via a link like this:

http://yoursite/reports/_layouts/WebAnalytics/Report.aspx?t=SummaryReport&l=sc

November 6, 2013

How to change the structure of sharePoint site in a site collection

Filed under: PowerShell, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 23:08

SharePoint sites can be created using a wrong URL name, or in an even worse case, under a wrong parent site. In both cases the site is located at a URL that is somehow not ideal for the users. If there is no content in the new site yet, it is probably the easiest to delete and re-create the site using the right name / at the right place. However, if the users created a lot of content in the site, we should find another solutions to handle the mistake. It is good to know that there are alternatives for such issues.

Solution 1: If the site is created under the correct parent site but with a wrong name, it is the easiest to fix the name via the SharePoint UI. As described in this post, you can change the name under Site Actions / Site Settings / Title, description, and icon. This method does not help to move sites between parent sites.

image

Solution 2: One of my colleagues, Verena has found another solution via PowerShell:

Get-SPWeb http://yoursite/siteX | Set-SPWeb -RelativeUrl subsite1/siteY

Using this method, we can not only rename the site, but we can move it under an existing subsite as well. It is important to point out, that based on our experience, this method does not work in the other direction, that means, we cannot move a site up in the hierarchy, like from under a subsite to the root site. Thanks Verena for this solution, and happy birthday! Birthday cake

Solution 3: Using the ServerRelativeUrl property of the SPWeb object we can alter the structure of the sites as well. This method seems to be pretty flexible, makes it possible to change the site relationships in both directions.

$web = Get-SPWeb http://yoursite/subsite1/siteX
$web.ServerRelativeUrl = “/yoursite/siteY”
$web.Update()

It is an interesting question, how the methods 2 and 3 above handle the case, when the site permissions are configured to be inherited from the parent site. Do they keep the permissions inherited from the original parent site? Or the permissions of the new parent site will be inherited? Recently we had to move sites using these methods, but in our case the sites had always their own permission sets, so I cannot answer this question right now.

September 10, 2013

Restarting SharePoint Timer Service on remote servers using PowerShell

Filed under: PowerShell, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 20:45

Recently we installed a new version of a custom SharePoint timer job together with a SharePoint administrator of our company. After installation, the Timer Service must have been restarted, to enforce reloading of the new version of the assemblies. My colleague used a remote desktop connection tool to log in on each of our front end / application servers (we have a few ones) and restarted the service using Service Manager. I asked him (and myself): Wouldn’t it be much more easier to restart the services from a single console, for example, using PowerShell?

After a quick research I came up with the solution (you should update the server names in the script, of course):

$computers = ("SPFrontEnd1", "SPFrontEnd2", "SPApp1", "SPApp2")
$computers | % { Restart-Service -InputObject $(Get-Service -Computer $_ -Name SPTimerV4) }

Pretty simple, isn’t it?

August 30, 2013

Granting the Limited Access permission level

Filed under: Security, SP 2010, Tips & Tricks — Tags: , , — Peter Holpar @ 02:01

I read statements like this several times on various blogs and in technical articles:

Limited Access is a special permission level that cannot be assigned to a user or group directly.” (source)

However, this is only the half of the truth.

Assume, you have a role assignment (SPRoleAssignment) that contains a reference to the Limited Access role definition (SPRoleDefinition):

web.RoleDefinitions["Limited Access"];

or

web.RoleDefinitions.GetByType(SPRoleType.Guest);  // “Users cannot be added explicitly to the Guest role” again… (source)

It’s true, that when you use the “standard” overload of the Add method of the SPRoleAssignmentCollection, SPRoleAssignmentCollection.Add(SPRoleAssignment) with this role assignment, you get an exception like this:

You cannot grant a user the limited access permission level.

But if you use the other overload, SPRoleAssignmentCollection.Add(SPPrincipal), it does just that, grants the principal the Limited Access permissions.

For example:

web.RoleAssignments.Add(group);

grants the group the Limited Access permission on the web site.

July 10, 2013

"Item cannot be more than 256 characters" warning at the PeopleEditor control

Filed under: SP 2010, Tips & Tricks — Tags: , — Peter Holpar @ 23:38

When working with the PeopleEditor control (either through a Person or Group field control on a list item edit form, or directly in a web part / application page) you might hit the limitation, that the control does not allow name resolution for strings longer than 256 characters. It is pretty annoying when you would like to copy a batch of user names from another application, like Word or Excel.

image

Based on the MS Knowledg Base article 2654222 it is a by design behavior. The proposed resolution:

You still can insert up to 200 users inside a SharePoint 2010 user control box. You just need to use the "Check Names" control in smaller batches.
For example, you insert a number of users that add up to less than 256 characters and click the "Check Names" control. You then add another batch of users and click "Check Names", and so on until you have added and resolved all of the desired users.

Although I’m understanding the reason of this limitation, I found the workaround everything but not user-friendly. Fortunately, there is a way to override this “feature”.

The limitation is regulated by the JavaScript variable g_MAX_LEN defined in entityeditor.js (debug version entityeditor.debug.js). When we inject the following script (either directly in the case of an application page, or through a CEWP / ScriptLink custom action in the case of an edit form) to the page, the limit is pushed up to 1000 characters. The change affects only the pages where the script block is injected. Of course, you should update the path to the jQuery script to match the location in your system.

<script src="/_layouts/jQuery/jquery-1.8.3.min.js"></script>
<script language="ecmascript" type="text/ecmascript">
function onPageLoad() {
 
g_MAX_LEN = 1000;
}
$(document).ready(onPageLoad);
</script>

The JavaScript variable (EntityEditor_ItemTooLong) of the corresponding error message is defined in a script block emitted by the RegisterScriptsAndVariables method of the EntityEditor class. If you would like to customize the message according to the new limit, you have to “override” the value of that variable as well.

Older Posts »

Create a free website or blog at WordPress.com.