Friday, March 8, 2019

The problem

We are using SharePoint 2013 in our on-premises farm. A week ago we introduced a new feature to one of our WSP solutions, which required us to undeploy and redeploy the solution. After the deployment, many of our sites (migrated from SP 2010) stopped working - whenever our customers would access the default.aspx page (the welcome page for some sites), they would be redirected to the "Page Not Found" page.

The solution

The ULS logs showed the following lines:

Relying on fallback logic in VghostPageManager::getGhostDocument() for document: 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Template\SiteTemplates\STS_SENS\default.aspx'
Relying on fallback logic in VghostPageManager::getGhostDocument() for document: 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\SiteTemplates\STS_SENS\default.aspx'
Cannot complete this action.  Please try again.
Failed to get document content data. Microsoft.SharePoint.SPException: Cannot complete this action.  Please try again. ---> System.Runtime.InteropServices.COMException: Cannot complete this action.  Please try again.    

 at Microsoft.SharePoint.Library.SPRequestInternalClass.GetGhostedFileContent(  

As you can see, the search for the ghosted default.aspx page is in the 12 and 14 hives, whereas we are using the 15 hive. Thus, the ghosted default.aspx page cannot be loaded.

Therefore, the solution that caused the discord needs to be undeployed and redeployed again, this time with the -CompabilityLevel:{14,15} clause. This will ensure that the old migrated sites, whose default.aspx page expects the compatibility level of 14, can be accomodated. We ran Install-SPSolution with this clause and everything now works great again. Nota bene - the compatibility level clause only applies to Install-SPSolution, not Upgrade-SPSolution.

Wednesday, September 21, 2016

Widening the Multiple Selection Box on a List Form

The Problem

The OOTB list form has a multiple selection box; many entries there are too wide to fit in the list box.

The Solution

SharePoint Designer 2013 and jQuery come to the rescue.

1) Open the site in SharePoint Designer.
2) Edit EditForm.aspx. Note that some lines will be highlighted in yellow - this means you should NOT edit them.
3) You will find the <WebPartPages:WebPartZone> tag followed by <ZoneTemplate> tag. Put your cursor there and then go to the Insert button on the ribbon. There, choose Web Part and find Content Editor.
4) A Content Editor web part is now on your form page. Now, you want to add a jQuery script there. After the </ID> closing tag, add this:

<Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor"><![CDATA[<script type="text/javascript">

$(document).ready(function(){

$('select[multiple=multiple]').width(300);
});
</script>
]]></Content>

(nota bene - on the line where you set the width, you can adjust the width the way you want or pick a specific multiple selection box; in my example, you pick all of them.

5) Rinse and repeat for NewForm.aspx.

Tuesday, April 14, 2015

Hiding OneDrive from Your SharePoint 2013 Pages

Suppose you don't want your customers, for whatever reasons, to access OneDrive from their SharePoint sites, yet you do want them to use Newsfeed and Sites. What to do?

This problem actually has quite a simple solution. Basically you want to write a script (in JavaScript) that would prevent OneDrive from being displayed. The <a> element that contains a link to <span>OneDrive</span> has an ID that always ends with "_ShellDocuments" (e.g., "ctl00_ctl53_ShellDocuments"), and each link has the "ms-core-suiteLink-a" CSS class. Armed with this theory, you can easily design the solution:

1) Create a new ASP.NET user control.
2) Create a site-collection scoped feature whose elements file refers to this control, e.g.

<Control Id="HideOneDrive" Sequence="10" ControlSrc="~/_controltemplates/15/HideOneDrive.ascx" />

3) Add this to your ASP.NET user control:

<script language="javascript">

    _spBodyOnLoadFunctionNames.push("_doHideOneDrive");

    String.prototype.endsWith = function(suffix) {
        return this.indexOf(suffix, this.length - suffix.length) !== -1;
    };

function _doHideOneDrive() {
        var x = document.getElementsByClassName('ms-core-suiteLink-a');
        var i;
        for (i = 0; i < x.length; i++) {
            if (x[i].id.endsWith("_ShellDocuments")) {
                x[i].style.display = 'none';
            }
        }
    }
<script>

That's it. Newsfeed and Sites stay, but OneDrive is gone.

Tuesday, January 13, 2015

Editing Managed Metadata Fields in the User Profile

The Problem

You're logged on to your MySite in SharePoint 2013, editing your user profile, when you suddenly notice your inability to edit certain fields. The error message is typically this:


 The Solution


Usually this means that the problem is with the Managed Metadata fields. The first order of business is to make sure that your Managed Metadata Service Application is properly provisioned and the corresponding service is running (in Services on Server.) But suppose that both are running OK (as it was in my case), yet you are still unable to edit the Managed Metadata field. Now, it is time to check the ULS log. There, you’re likely to see this:

Error encountered in background cache check System.Security.SecurityException: Requested registry access is not allowed.   
 at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplication.GetMOSSInstallPath()   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.GetChannel(Uri address, Boolean& cachedChannel)   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.<>c__DisplayClass2f.<RunOnChannel>b__2d()   
 at Microsoft.Office.Server.Security.SecurityContext.RunAsProcess(CodeToRunElevated secureCode)   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.RunOnChannel(CodeToRun codeToRun, Double operationTimeoutFactor)   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.ReadApplicationSettings(Guid rawPartitionId)   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.get_ServiceApplicationSettings()   
 at Microsoft.SharePoint.Taxonomy.MetadataWebServiceApplicationProxy.TimeToCheckForUpdates()   
 at Microsoft.SharePoint.Taxonomy.Internal.TaxonomyCache.CheckForChanges(Boolean enforceUpdate)   
 at Microsoft.SharePoint.Taxonomy.Internal.TaxonomyCache.<LoopForChanges>b__0()  The Zone of the assembly that failed was:  MyComputer.

Basically this means you probably ran some update that screwed up the registry access of the application pool account under which the Managed Metadata service is running.  You will need to run the following command:

Psconfig –cmd secureresources

This does the trick!

















Monday, October 6, 2014

The Treacherous SPFolder

Let's talk about SPFolder. This class is not as simple as it seems, and can cause a lot of headaches if you are not prepared. The reason is that a non-null object of the SPFolder class does not always point to a valid folder. First of all, the Exists property has to be set to true. This is a bit unusual for the SharePoint object model to have a class whose non-null objects are only valid if the Exists property is set to true. Lists, sites, and other SharePoint objects do not work this way.

But even if the Exists property happens to be set to true, the folder is not necessarily valid. It all depends on how you obtain the reference to the folder. Consider the GetFolder() method of the SPWeb class. You can pass a folder URL to this method and get a valid folder. Sort of. Because if you are not careful and are trying to add files to the folder whose reference you obtained in this fashion (and dutifully checked whether the Exists property is set to true), you will get the System.IO.DirectoryNotFoundException - and have no idea what is causing it. Let's see, then, what GetFolder() is and how to use it properly.

Suppose there is some site collection with a subsite - let's assume it is named "t." Suppose there is a document library there named Docs. This document library has a folder F1; the F1 folder, in turn, has a folder named F2. Our task is now to obtain an SPFolder reference to the F2 folder. If we call the GetFolder() method from the t subsite, the following simple PowerShell script:

cls
$s = Get-SPSite "https://main.mini.tws.com/sites/Test12345/"
$w = Get-Spweb "https://main.mini.tws.com/sites/Test12345/t"

$f2 = $w.GetFolder("Docs/F1/F2")
$f2.ServerRelativeUrl
$f2.ParentFolder

$w.Dispose()
$s.Dispose()


will return the valid folder:

 /sites/Test12345/t/Docs/F1/F2
EffectiveRawPermissions   : FullMask
EffectiveAuditMask        : 284
ProgID                    :
ParentFolder              : Docs
ParentWeb                 : t
Url                       : Docs/F1
UniqueId                  : bd2b9455-dc49-4378-854c-fb8d1077d881
ItemCount                 : 1
Name                      : F1
ServerRelativeUrl         : /sites/Test12345/t/Docs/F1
WelcomePage               :
Files                     : {}
SubFolders                : {Docs/F1/F2}
ContainingDocumentLibrary : 0f96ab7d-227f-422c-9c2b-6418a6b84125
RequiresCheckout          : False
DocumentLibrary           : Docs
Exists                    : True
Item                      : Microsoft.SharePoint.SPListItem
Properties                : {vti_folderitemcount, vti_level, vti_listname, vti_listbasetype...}
Audit                     : Microsoft.SharePoint.SPAudit
ParentListId              : 0f96ab7d-227f-422c-9c2b-6418a6b84125
UniqueContentTypeOrder    :
ContentTypeOrder          : {Document}


So far so good, right? As expected, ParentWeb is t, and the ContainingDocumentLibrary and ParentListId all have the valid list id. And, of course, Exists is set to True.

Suppose now that we call GetFolder from the root web, pointing to the folder URL, like in this script:

cls
$s = Get-SPSite "https://main.mini.tws.com/sites/Test12345/"
$w = Get-SPWeb "https://main.mini.tws.com/sites/Test12345/t"

$f = $s.RootWeb.GetFolder("t/Docs/F1/F2")
$f.ServerRelativeUrl
$f.ParentFolder

$w.Dispose()
$s.Dispose()


The result will be:

 /sites/Test12345/t/Docs/F1/F2
EffectiveRawPermissions   : FullMask
EffectiveAuditMask        : 284
ProgID                    :
ParentFolder              : t/Docs
ParentWeb                 : Test12345
Url                       : t/Docs/F1
UniqueId                  : bd2b9455-dc49-4378-854c-fb8d1077d881
ItemCount                 : 1
Name                      : F1
ServerRelativeUrl         : /sites/Test12345/t/Docs/F1
WelcomePage               :
Files                     : {}
SubFolders                : {}
ContainingDocumentLibrary : 00000000-0000-0000-0000-000000000000
RequiresCheckout          : False
DocumentLibrary           :
Exists                    : True
Item                      :
Properties                : {vti_folderitemcount, vti_level, vti_listname, vti_listbasetype...}
Audit                     : Microsoft.SharePoint.SPAudit
ParentListId              : 00000000-0000-0000-0000-000000000000
UniqueContentTypeOrder    :
ContentTypeOrder          : 


If you look carefully, something is not right. Even though Exists is set to True, ContainingDocumentLibrary and ParentListId are sent to empty guids - this basically means you cannot add items to this folder, as you will get the above-mentioned System.IO.DirectoryNotFound exception! Also, note that this strange behavior occurs in BOTH SharePoint 2010 and SharePoint 2013.

The bottom line is that if you call GetFolder() from the web other than the one that directly contains the library with the folders, you are not going to obtain a valid folder. So, what to do if your requirements call for adding new files to folders identified by their URLs?

Luckily, there is a way around it. First of all, here is an extension method that returns a containing web for the folder:

  public static SPWeb GetContainingWeb(this SPFolder folder)
  {
            SPWeb web = null;
            using (SPSite site = new SPSite(folder.ParentWeb.Site.ID))
            {
                using (SPWeb someweb = site.OpenWeb(folder.Url, false))
                {
                    web = someweb;
                }
            }
            return web;
  }


 We can now use it to obtain a reference to a corrected folder - we obtain it by parsing the url.

public static SPFolder GetCorrectedFolder(this SPFolder folder)
{
            SPWeb containingWeb = folder.GetContainingWeb();

            string folderUrl = folder.Url;

            SPFolder correctedFolder = containingWeb.GetFolder(folderUrl);

            string[] parts = folderUrl.Split(new char[] { '/' });

            for (int i = 0; i < parts.Length - 1 && !correctedFolder.Exists; i++)
            {
                int pos = folderUrl.IndexOf("/");
                folderUrl = folderUrl.Substring(pos + 1);
                correctedFolder = containingWeb.GetFolder(folderUrl);
            }
            return correctedFolder;
 }


Now we can obtain a valid reference to a folder no matter what SPWeb we use to call the GetFolder() method on.
 

Monday, October 14, 2013

SharePoint Developer Resumes

As a SharePoint developer/architect, I have had to review dozens of resumes of SharePoint developers. Here are a few tips I would like to recommend.

1. Your resume is your writing sample. Typos, misspellings, and grammar errors indicate that you didn't take your time proofreading your resume, you are not attentive to detail,  and/or you have some difficulties with your written communication - and this does not show you as a worthy candidate.

2. Ditto for the cover letter. Don't neglect your correspondence with the recruiters. You never know when your messages are forwarded to those people who read your resume - and if your writing is awkward and/or full of grammar errors, you are quite likely to be disqualified.

3. Make sure your resume is not too long.  I've seen a few consultants' resumes that were 14 pages long! It takes a lot of work to proofread and it is very easy for important content to get lost in such a long resume - and a lot of us who read resumes don't get to page fourteen (or even page seven, for that matter.)

4. ... but not too short, either.  You don't want to miss any selling points. Especially the ones that make your resume stand out. For an experienced developer, I think a good resume size is two to four pages.

5. The first page is the most important. Make sure your skills and the most recent experience are all mentioned on the first page. Remember, if the first page is so-so, the next page is not likely to be read.

6. Call each technology by its proper name. I've seen a lot of resumes that talk about "MOSS 2010," "SharePoint 2012," etc., and even some resumes where SharePoint is called "Share point." This shows lack of attention to detail. Not good.

7. Don't mention any obsolete technologies and certifications. Some candidates mention Java 2 or certificates in .NET Framework 1.1 in their resumes. This just wastes valuable space. A possible exception is SharePoint; if someone worked with SharePoint 2003 and mentioned it in her SharePoint developer resume, this shows that the person has a lot of experience with different versions of SharePoint and is able to appreciate the features that came in the most recent versions.

8. Make sure no sentence in your resume indicates you don't know what you're talking about. Some resumes contain sentences on how the candidate "designed OOTB features" or "designed SharePoint site templates." If I see this in anyone's resume, I take the former first lady's advice and just say no.

9. Watch what you copy from other people's resumes.  I've seen the same awkward sentence about the candidate's supposed experience appear in three or four resumes of other people; then I searched for that sentence on Google and found a lot of other resumes containing this very sentence. If the candidate cannot describe his or her experience in his or her own words, he or she probably won't do so well on the interview - and thus it is not worth talking to him or her further.

10. Don't make your resume a laundry list. This makes the resume too long and too boring. Instead of listing every little thing you worked on, you'd be better off listing your accomplishments and what you did with SharePoint and other technologies to contribute to the projects you worked on. Keep it brief and to the point. Remember, your goal is to get whoever reads your resume to be interested in your resume enough to call you and invite you to an interview.

11. In particular, don't list the trivial stuff. I've actually seen resumes where the candidate talked about how he or she created document libraries or sub-sites. If you did it via the UI, it is really a user task, not a developer task; if you did it programmatically, it is still way too trivial to be mentioned in your resume.

12. ... but do mention the interesting things you did. If you integrated SharePoint with some other systems such as SAP, make sure you mention it. If you wrote your own service application, make sure you mention it. If you designed some nontrivial workflow, make sure you mention it. Not only does it make your resume stand out, it also leads to interesting interview questions later on (and when I interview candidates, I like the ones with who I can have an interesting discussion.)

13. If you mentioned something in your resume, be prepared to discuss it during the interview. If you're calling yourself a SharePoint workflow expert in your resume, it is a fair game for me to ask you questions about what a replicator is or how to debug a workflow. If you cannot answer these questions correctly, it makes me wonder where else you are overselling yourself in your resume.

14. If you seek a development position, make sure your resume shows off your development experience. I've seen some resumes where the candidate for a development position seemed more like an admin or a power user than a developer. This can indicate a poor fit.

15. Don't forget about the education section. It is always nice to interview educated people. If you went to a good school, make sure the education section is prominently listed on your resume (if you graduated fairly recently, you might even want to put the education section on the first page.)

16. Keep irrelevant things off your resume. Don't list things such as your birthplace, marital status, hobbies, etc. on your resume. None of these things will help you get invited to an interview.

17. Keep the less important stuff on the last page. I usually put my citizenship, security clearance, membership in professional organizations, and foreign languages on the last page.

If you are looking for a SharePoint developer position (mid-level or senior) and are in Northern Virginia, you're most welcome to send me your resume. We have a few very good opportunities here.

Thursday, September 19, 2013

Identifying Problem Publishing Image Libraries

The problem:

In one of my previous posts (http://kiwiboris.blogspot.com/2013/08/migrating-publishing-images-libraries.html) I talked about publishing images libraries and how the images from these libraries might need to be re-migrated and the specific steps needed to create such libraries and perform the migration. It is nice, however, to identify all such problem libraries in every site collection on the farm. Usually, when these libraries are not migrated correctly, one of the symptoms is that each image is checked out by the farm admin (the reason is that since the metadata for these images is faulty they cannot be checked in.)

The solution:

First of all, it is useful to remember that the base template for the publishing images library is 851 (fa propos, it is 850 for the publishing pages library.) Also, we are only interested in the libraries that have at least one item, each of them checked out. The following script will do the job:

cls

$w = Get-SPSite "https://main.coolsite.myserver.com"
$wa = $w.WebApplication

$wa.Sites | % {
   $_.AllWebs | % {
    $_.Lists | Where-Object { $_.BaseTemplate -eq 851 -and $_.Items.Count -gt 0} | % {
       $itemsCheckedOutCount = $_.Items | Where-Object { $_.File.CheckOutType -ne "None" } | Measure-Object
       if ($itemsCheckedOutCount.Count -eq $_.Items.Count)
       {
            Write-Host "All the images in the document library" $_.Title "in site" $_.ParentWeb.URL "are checked out"
       }
    }
   }
}

$w.Dispose()