Welcome to MSDN Blogs Sign in | Join | Help

Rodney Viana's (MSFT) Blog

Post on Sharepoint, IIS and C#
Blobcache will not work if the request url is not in the Alternate Access Mapping list

There are scenarios on which you can access a unique site collection using different URLs even if these URLs are not listed in the Alternate Access Mapping (AAM) of the application. This normally does not cause any problem and even though you should not, you can live without adding DNS/Netbios aliases to AAM. If you plan to use blob cache, however, make sure that you list all URLs you may think of in the AAM.

One probable and common scenario is that on which you have multiples Web Front End (WFEs) and they are reached via Network Load Balancer (NLB). You did not add any host header when you created the web application and yet you can access the web application using both the local server name (eg. MOSS-WFE01) or the DNS entry (eg. http://myportal.contoso.com). However, when you add blob cache functionality to the site (see how to do this here), the cache does not seem to (and actually does not) work.

It happens because blob cache serving and caching happens in a level above SharePoint. It relies on the class HttpApplication rather than WebApplication. If necessary it goes to SharePoint to get the content and cache or just skip the file. If the file is already cached it retrieves from the file system rather than via SharePoint. At some point the blob cache trims the resource accordingly to user permission. At this point SharePoint has to be inquired and if part of the URL containing the web application is not in the AAM, SharePoint will assume the user has no rights and will not return anything at all.

How to verify if I am using the correct Alternate Access Mapping in my Network Load Balancer

Knowing the exact host header coming from the NLB is not so easy. Depending on the configuration, the NLB may use just an IP address or a FQDN. In order to find out do the following.

1. Enable IIS logging by selecting Web Sites, right-clicking and choosing properties.

image

2. In Properties/Advanced make sure Host (cs-host) is enabled.

image

3. Clear the cache in the web browser in any workstation (make sure the request will go through the NLB) and hit a couple of pages to populate the log

4. Navigate to the log file in the server you made the change (normally c:\windows\system32\LogFiles) and fin the latest file. You will something like this (in this example cs-host = team.learning.local):

image

5. Make sure the host header is listed in the Alternate Access Mapping (Central Administration | Operations | Alternate Access Mapping | Edit Public Url | Choose Web Application):

image

The fine art of updating MOSS 2007 and WSS 3.0

Let's face it, updating Sharepoint is not an easy task. It is true for both Microsoft Office Sharepoint Server (MOSS) 2007 and Windows Sharepoint Services 3.0. Let's highlight some points:

  1. MOST IMPORTANT: MOSS is built on top of WSS and you have to apply WSS and MOSS updates on MOSS installation
  2. You have to install localized versions of the updates even if you only uses English
  3. If you have MOSS, you have to install localized version of the updates even if you use English only
  4. VERY IMPORTANT: A later update patch DOES NOT include previous patches, so you may have to install a series of patches to get to a specific version. For example, August CU requires you to install SP1, Infrastructure Updates and other patches to be installed before.
  5. The only reliable information about current and past update paths can be found at http://blogs.msdn.com/sharepoint
  6. Before December Cumulative Update (CU) Uber Package, you were supposed to download a patch for each additional language you have installed in your farm.

 

How to update:

First, know where you are now. The way to know your version is going to Central Administration | Operations | Servers in Farm. The version will be shown in the page. This version will not tell you all as you have other components besides the server version. Some people, including me, prefer to check the version in the files. To get the version of the files, we use SPSReports.

SPSReports will generate a large amount of captured files. The file version will be available at <SERVERNAME>_WSEVer.TXT. This is an excerpt of such file (showing SP1 files):

    c:\program files\common files\microsoft shared\web server extensions\12\bin\*.*
--a-- W32i   DLL ENU         6.6.7.5 shp  1,030,144 02-21-2007 dbghelp.dll
--a-- W32i   APP   -  12.0.4518.1016 shp     23,824 11-08-2006 hcinstal.exe
--a-- W32i   DLL   -  12.0.4518.1014 shp    123,232 10-26-2006 microsoft.office.irm.formprotector.dll
--a-- W32i   DLL   -  12.0.4518.1014 shp     38,736 10-26-2006 microsoft.office.irm.msoprotector.dll
--a-- W32i   DLL   -  12.0.4518.1014 shp     34,640 10-26-2006 microsoft.office.irm.ofcprotector.dll
--a-- W32i   DLL   -  12.0.6211.1000 shp     92,000 08-24-2007 msscntrs.dll
--a-- W32i   APP   -  12.0.6211.1000 shp    282,496 08-24-2007 mssdmn.exe
--a-- W32i   APP   -  12.0.6211.1000 shp    159,648 08-24-2007 mssearch.exe
--a-- W32i   DLL   -  12.0.6211.1000 shp    694,104 08-24-2007 mssph.dll
--a-- W32i   DLL   -  12.0.6211.1000 shp  2,059,104 08-24-2007 mssrch.dll
--a-- W32i   DLL   -  12.0.6212.1000 shp  1,079,200 09-02-2007 offparser.dll
--a-- W32i   DLL   -  12.0.6219.1000 shp  3,012,136 11-15-2007 owssvr.dll
--a-- W32i   DLL   -  12.0.6211.1000 shp     26,496 08-25-2007 oisimg.dll
--a-- W32i   DLL   -  12.0.4518.1016 shp     26,456 11-08-2006 oleparser.dll
--a-- W32i   DLL   -  12.0.6211.1000 shp    357,264 08-25-2007 onetnative.dll
--a-- W32i   DLL   -  12.0.6219.1000 shp  1,983,528 11-16-2007 onetutil.dll
--a-- W32i   DLL   -  12.0.4518.1014 shp    100,144 10-27-2006 osafehtm.dll
--a-- W32i   APP   -  12.0.6211.1000 shp     58,232 08-25-2007 owstimer.exe
--a-- W32i   DLL   -  12.0.4518.1014 shp    400,200 10-26-2006 microsoft.office.policy.dll
--a-- W32i   DLL   -  12.0.4518.1014 shp     52,040 10-26-2006 microsoft.office.workflow.tasks.dll
--a-- W32i   DLL   -  12.0.6211.1000 shp  6,407,584 08-24-2007 microsoft.sharepoint.portal.dll
(....)

 

Depending on the product, there will be files with more significance than others and be aware that not all files will be updated to the latest build. In the excerpt above oleparser.dll is still in version 12.0.4518 (RTM version) while owssrv.dll is in SP1 (12.0.6219).

 

Checking your version:

  • For WSS Global: Check OWSSVR.dll
  • For WSS Local: normally by checking the localized javascript version and comparing with the global javascript files and by mssmsg.dll (it may vary - see Knowledge Base method later in this post)
  • For MOSS Local: normally by checking the localized javascript version and comparing withe the global javascript files and by Htmlchkr.dll (it may vary - see knowledge Base method later in this post)
  • For MOSS Global: Check Microsoft.Sharepoint.Portal.dll (also make sure all other updates as WSS and DLC are compatible with the build, I will talk about it later)
  • For Document Lifecycle Management (DLC) aka Workflow: highest version between microsoft.office.policy.dll and microsoft.office.workflow.tasks.dll
  • For Infrastructure Updates (IU): It varies (see Knowledge Base method later in this post)

Checking what takes to get up-to-date

As I told you before, the Sharepoint Team blog is the most reliable source of update path. If you want to update to the latest recommended version or just fix a previous update not working so well because you did not know that WSS updates was also mandatory, you better check the blog. Other reason to fix a previous update is when your javascript yields an error every time you try to edit a rich edit field or it only yields an error when you are not editing in English.

One of the problems I see occasionally in my work at Microsoft is clients who were not aware they had to install the language patch update for every language they have in addition to the local English patch. One symptom of the problem is the Javascript errors I mentioned. Sometimes they prefer to wait for SP2 and avoid to update in all Cumulative Updates released. Before December CU, all other patches required a long list of prerequisites. My colleague Robert Gullick put together a list for all paths up to December CU.

The new updates make things easier. For example, October CU required at least 4 patches (if you don't have other languages installed) while December CU only requires two patches no matter how many languages you have installed.

Let's take a look at the requirements for having October CU in MOSS (i.e. update path):

  • KB936988 - WSS SP1 + LanguagePack SP1 - 6219
  • KB957109 - WSS August CU Local - 6327
  • KB957691 - WSS October CU Global - 6332
  • KB936984 - MOSS SP1 + Language Pack SP1 - 6219
  • KB955586 - MOSS DLC Component Update Local - 6324
  • KB955937 - MOSS Excel Services security update - 6324 *
  • KB958569 - MOSS DLC Component Update Global - 6331
  • KB958567 - MOSS October CU Local - 6331
  • KB957693 -MOSS October CU Global - 6331

If you had installed any of the KBs in the path you don't have to install the KB again. And now we finally will talk about how to use the KB method.

How to identify if the KB is already installed (Knowledge Base method)

Unfortunately the KBs are not visible in Add/Remove Programs in Control Panel. But the information is in the registry. Let's say I want to check if KB958569 is installed or not.

  • Open Registry Editor (Start | Run | Regedit.exe)
  • Go to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
  • Press Ctrl+F to start a search
  • Look for the KB number by entering only the number without the prefix (eg. 958569)
  • If you find it under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall it is because it is installed.
  • This method is not so reliable because it does not keep track of past KB installed but it can be a good start
  • You can issue the command "reg export HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall uninst.txt" to dump the registry info in the text file.

How to download a specific KB hotfix (don't forget the local patches also)

Some hotfixes do not have an immediate public download place. You have to request. Please see the sample below for KB 958567 which is local update (and must be installed for each language you have in your environment).

The KB is located here:

http://support.microsoft.com/kb/958567

for other KBs, just change the number in the url.

The hotfix can be download here:

http://support.microsoft.com/hotfix/KBHotfix.aspx?kbnum=958567

Notice that you will be offered only the English version initially. Click on “Show hotfixes for all platforms and languages” and it will show for all languages and architectures (x86 and x64).

clip_image002

Choose all languages and architectures that apply. You will receive an e-mail with instructions on how to download the patch.

Please follow these for all patches.

Getting Sharepoint Site/Web Context in a Console Application

I had recently faced a problem which I believe other people will face at least once: how to get the context from a URL in a C# Console Application. I needed to use ProfileLoader which requires a context. You cannot get current context when you run an application outside a Sharepoint page (like in Windows or Console Application). The solution below gets a context from the topology (which happens to be deprecated but works) and get all properties from the profile. See the sample code below:

 

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.Office.Server;
using Microsoft.SharePoint.Portal;
using Microsoft.SharePoint.Portal.Topology;
using Microsoft.SharePoint;
using System.Web;

namespace TestProperties
{
    class Program
    {
        static void Main(string[] args)
        {
            //get portal site context from topology
            string strUrl = "
http://myserver";
            TopologyManager tm = new TopologyManager();
            PortalSite ps = tm.PortalSites[new Uri(strUrl)];
            PortalContext pc = PortalApplication.GetContext(ps);
            ServerContext sc = pc.ServerContext;

            PropertyCollection props = ProfileLoader.GetProfileLoader(sc).GetUserProfileManager().Properties;

            foreach (Property prop in props)
            {
                if (prop.Type != PropertyDataType.Binary && prop.DefaultPrivacy == Privacy.Public)
                {
                    Console.WriteLine("{0} - {1}", prop.DisplayName, prop.Name);
                }
            }

        }
    }
}

How to test the Mail Settings for a Sharepoint Web Application

WSS and MOSS enable outgoing e-mail configuration and most of the time this configuration is straightforward. As of matter of fact you do not have much to configure as shown below:

image

Figure 1 - Outgoing e-mail settings

 

This same configuration is used for alerts and notifications as it is for invitations. Most of the e-mail sending is accomplished by OWSTIMER.EXE (The Sharepoint Timer Service) which run as the Farm Administrator Account in all servers in the farm. When you have alerts sending e-mail most of the time and failing just occasionally, it is possible that one of the servers is not able to deliver the e-mail. Testing e-mail delivery in WFEs (Web Front Ends) is easier, since adding someone to a site and sending a welcome (see screenshot below) mail will force Sharepoint to send the e-mail from the WFE serving the request and the result is shown immediately when you press OK. It may bring some extra configuration to identify the WFE if you are using NLB (Network Load Balancer), but nothing compared to OWSTIMER.EXE identification problem.

image

Figure 2 - Sending Welcome mail is a fast to way to identify connection problems as the result is in the next page

 

However if you still have problems to identify why e-mails are not being delivered using the Welcome Page or the server you suspect is not serving pages you may need to run a few other tests. First, you have to check if the suspect server is reaching the SMTP server. You can use the basic ping and then try the telnet connection to see if you get the right response as specified in KB153119. If everything seems correct and you still have problem, try the following code:

 

(UPDATED)

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using System.Net.Mail;

namespace SendMail
{
    public class MailServerInfo
    {
        protected string server;

        public string Server
        {
            get { return server; }
        }
        protected string from;

        public string From
        {
            get { return from; }
        }
        protected string replyTo;

        public string ReplyTo
        {
            get { return replyTo; }
        }

        public MailServerInfo(SPSite spSite)
        {
            server = "(empty)";
            from = "(empty)";
            replyTo = "(empty)";
            SPWebApplication spWebApplication = spSite.WebApplication;
            if (spWebApplication.OutboundMailServiceInstance != null)
            {
                from = spWebApplication.OutboundMailSenderAddress;
                replyTo = spWebApplication.OutboundMailReplyToAddress;
                SPOutboundMailServiceInstance smtpServer = spWebApplication.OutboundMailServiceInstance;
                server = smtpServer.Server.Address;
            }

        }

        public string ServerInformation
        {
            get
            {

               return String.Format("Site Specific - server: {0}, From: {1}, ReplyTo: {2}",
                        server, from, replyTo);

            }
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("Sharepoint e-mail configuration tester");
            Console.WriteLine("======================================");
            Console.WriteLine("Written by Rodney Viana");
            Console.WriteLine("More info at
http://blogs.msdn.com/rodneyviana\n");
            Console.WriteLine("This sample application is supplied \"AS IS\"");
            Console.WriteLine("It is ONLY for demonstration/proof of concept purposes");
            Console.WriteLine("If you DO NOT agree with the license at:\n\thttp://www.codeplex.com/rodneyviana/license\n\tPress Ctrl+C");
            Console.Write("Press any other key to continue...");
            Console.ReadKey();
            Console.Write("\n\nPlease enter url (eg.:
http://localhost):");
            string siteUrl = Console.ReadLine();

            try
            {
                using (SPSite spSite = new SPSite(siteUrl))
                {
                    using (SPWeb web = spSite.OpenWeb())
                    {
                        if (web == null)
                        {
                            Console.WriteLine("Web could not be found (OpenWeb returned null). Aborting.");
                            return;
                        }
                        Console.WriteLine("The site has been found at " + web.Url);
                        Console.Write("Please enter recipient login name (eg. domain\\administrator): ");
                        string login = Console.ReadLine();
                        string displayName = "";
                        string emailAddress = "";
                        try
                        {
                            SPUtility.GetFullNameandEmailfromLogin(web, login, out displayName,
                                out emailAddress);
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("Unable to get user information.");
                            Console.WriteLine("Error: "+ex.Message);
                            Console.Write("Press any key...");
                            Console.ReadKey();

                            return;
                        }
                        Console.WriteLine("Display Name: {0}\nE-mail: {1}", displayName,
                            emailAddress);
                        string subject = "Test message";
                        string body = "Testing app message";
                        MailServerInfo serverInfo = new MailServerInfo(spSite);

                        string server = serverInfo.Server;
                        string replyTo = serverInfo.ReplyTo;
                        string from = serverInfo.From;

                        Console.WriteLine(serverInfo.ServerInformation);
                        Console.WriteLine("Trying to send e-mail via Sharepoint...");

                        if (!SPUtility.SendEmail(web, true, true, emailAddress, subject, body))
                        {
                            Console.WriteLine("Error: Unable to send e-mail via Sharepoint");
                            Console.WriteLine("Trying via SMTP...");

                            SmtpClient client;
                            try
                            {
                                client = new SmtpClient(server);
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine("Could not contact server");
                                Console.WriteLine("Error: " + ex.Message);
                                Console.Write("Press any key...");
                                Console.ReadKey();

                                return;
                            }
                            try
                            {

                                client.Send(from, emailAddress, subject, body);

                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine("Unable to send via SMTP client");
                                Console.WriteLine("Error: " + ex.Message);
                                Console.Write("Press any key...");
                                Console.ReadKey();

                                return;
                            }
                            Console.WriteLine("E-mail has been sent successfully via SMTP client");

                        }
                        else
                        {
                            Console.WriteLine("E-mail has been sent successfully via Sharepoint");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unable to find url.");
                Console.WriteLine("Error: " + ex.Message);

            }
            Console.Write("Press any key...");
            Console.ReadKey();

        }
    }
}

 

You can download project and executable at  http://www.codeplex.com/rodneyviana/Release/ProjectReleases.aspx?ReleaseId=19103.

If you are not much in software development you can extract only the .exe file (SendMail\SendMail\bin\Debug\SendMail.exe) and run it without installation. This application will send an e-mail using the farm's configuration. Enter the url of a valid site and a valid user login name which will be the recipient of the test message. The application will retrieve the e-mail information from the user login name. You must run the application from one of the servers in the farm.

 

clip_image001

Figure 3 - The application will resolve the recipient e-mail address as well as the farm's outgoing e-mail configuration

 

If the configuration is correct the intended recipient will receive the following e-mail:

 

clip_image002

Figure 4 - E-mail will be received if everything is configured correctly

 

If you can receive this e-mail and still are unable to receive e-mail from sharepoint check the Application Pool credential (in case of Welcome page) or web farm administrator account (in case of alert and other e-mails sent by OWSTIMER.EXE).

Org Chart Web Part - Part I - Overview and Download

In this post we are going to explore User Profiles by extending the Organizational Hierarchy in MOSS. I've posted in Codeplex both installer and code for the Organizational Chart Hierarchy web part. This post will only discuss installation and configuration. I will discuss the code in posts to come.

What this webpart does:

This web part reads and analyzes the user profiles in MOSS and creates a full hierarchical organizational chart.  See screenshot below:

image

 

What I need to make it work:

1. It requires MOSS (Microsoft Office Sharepoint Server) 2007.

2. It requires you have imported profiles from you AD.

3. In order to have the hierarchy displayed correctly you have to include managerial and department information for all users who have manager (see the example in the snapshot below).

image

4. You have to edit the departmentconfig.xml file if you want links to the department sites. After the install, this file normally is found at: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS\OrgChartPart\. This file is only available after the install is complete.

5. Just edit, add or delete <Mapping Department="[Department Name As in Profile]">[url]</Mapping> entries as appropriate. The mapping must be exact. As the example below (this entry is case sensitive):

<Mapping Department="Operations">http://myweb/sites/operations</Mapping>

 

Installing Steps:

1. Download the latest release here: http://www.codeplex.com/orgchartpart/Release/ProjectReleases.aspx?ReleaseId=15590 and save in a folder you will remember later (eg. c:\downloads).

2. Go to MOSS Bin folder, commonly at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN

3. Run the following commands from the command prompt when logged as farm administrator (assuming the .wsp file is in C:\Downloads):

stsadm -o addsolution -filename c:\downloads\OrgChartPart.wsp
stsadm -o deploysolution -name c:\downloads\OrgChartPart.wsp -allowgacdeployment -immediate
stsadm -o execadmsvcjobs

4. Go to the shared services provider you use for personalization in Central Administration.

5. Choose Personalization services permission under User Profiles and My Sites.

6. Make sure NT AUTHORITY\Authenticated Users has "Personal Features" and "Personal Site" rights.

image

 

7. Go to the site collection you want to include the web part (eg. http:/myweb).

8. Go to Site Actions | Site Settings | Modify All Site Settings

9. Go to Site Collection Features under Site Collection Administration

10. Activate Org chart Web Part

image

 

11. Verify if OrgChartPart is available in the Web Part Gallery of the Site Collection (Site Actions | Site Settings | Galleries | WebParts)

image

 

12. Add the web part wherever you want in a page

13. Make sure you adjust the web part to render correctly:

image

 

Notes:

  • The beautiful tree layout in Javascript was developed by the talented Emilio CL and can be found here: http://www.codeproject.com/KB/scripting/graphic_javascript_tree.aspx
  • Don't try it in a large Enterprise with over 500 user profiles. It will work but will be slow. A few modifications are necessary to enable better performance.
  • Next post will be about the source code.
Populating BDC fields from a query string in MOSS (Microsoft Office Sharepoint Server) 2007

Whomever tried to populate a MOSS (Microsoft Office Sharepoint Server) form field using Javascript noticed that it is far from being straightforward. The secret is in the undocumented core.js After a client's request I was able to come up with a not so-intrusive Javascript solution.

You have to open the site in Sharepoint Designer, navigate to the list you want (in my example I used a list called "Public Details"), look for a spot in the html code and copy the Javascript code below to both EditForm.aspx and NewForm.aspx.

For each list in your site there will be a folder. For instance, let’s say you have a list called Public Details. In Sharepoint Designer you should edit files under site\Lists\Public Details as shown below (be aware these pages will be unghosted after that):

image

To test the application, just add the query string ?BDC=<value> to the url (eg. http://portal/Lists/Public+Details.aspx?BDC=123). See code below:

 

<script type="text/javascript" language="javascript" for="window" event="onload">

var elems = document.getElementsByTagName("DIV");
var bdcString="";

var queryString = document.location.search.split("&");

for (var i=0;i<queryString.length;i++) {
if ((queryString[i].split("=")[0]=="BDC") || (queryString[i].split("=")[0]=="?BDC")) bdcString=queryString[i].split("=")[1];
}

if(bdcString == "") return;

for (var i=0;i<elems.length;i++) {

if (elems[i].id.indexOf("_upLevelDiv") > 1)
{
        var subs = elems[i].id.substring(0,elems[i].id.indexOf("_upLevelDiv"));
        var upLevel = document.getElementById(subs+"_upLevelDiv");
        if(upLevel == null)
        {
         alert("error Up Level is invalid: "+subs+"\n"+upLevel);
         return;
        }
        var downlevel=document.getElementById(getSubControlID(subs, g_EntityEditorDownLevelId));
        downlevel.value = bdcString;
        var editor=document.getElementById(subs)
        SetInnerText(upLevel, bdcString);
      copyUplevelToHidden(subs);       
  }
}

</script>

Page view tracker