Friday, 28 October 2011

Writing to ULS log from Sharepoint e-mail event receiver

In case you are trying to use Sharepoint event logging infrastructure and its SPDiagnosticsService class from Sharepoint e-mail event receiver to write to the Windows Event Log or Sharepoint ULS log you might get this error: 

The source was not found, but some or all event logs could not be searched.  Inaccessible logs: Security

Or even worse, if you are not using Visual Studio 2010 debugger, but only rely on Event Viewer, you could see this error in Applications and Services Logs > Microsoft > Sharepoint Products > Shared > Operational:

An error occurred while processing the incoming e-mail file C:\inetpub\mailroot\Drop\3692ac8b01cc95a800000006.eml. The error was: Object reference not set to an instance of an object.

As you might know, SPEmailEventReceiver is running in owstimer.exe process which is actually "Sharepoint 2010 Timer" service. This service by default runs under NETWORK SERVICE account which does not have enough permissions to the Security log and this is the reason why SPDiagnosticsService class methods WriteTrace and WriteEvent will fail.

The solution I found is to add the NETWORK SERVICE account to the "Performance Log Users" Windows Security group and then restart the Sharepoint 2010 Timer service.

Tuesday, 18 October 2011

Use powershell to document your Hyper-V installation

I want the share this little script I wrote that helped me to document Hyper-V R2 cluster installation. This particular installation had more than 30 virtual machines and I had to document virtual machines properties and disk configuration.

The benefit of this script is that it merges output from two powershell commands, Get-VM and Get-VirtualHardDisk.

$vmmserver = Get-VMMServer -ComputerName SCVMMSERVER

$vms =  Get-VM | Sort-object Name | where {$_.HostGroupPath -like "All Hosts*"}

foreach ($vm in $vms)
{
    $disks = $vm | Get-VirtualHardDisk
        foreach($d in $disks) {

        new-object PSObject -prop @{
        VMName = $vm.Name
        Type = $d.VHDType
        "Disk name" = $d.Name
        "Size GB" = [math]::truncate($d.Size / 1GB)
        }

        }

}

The script generates this output:

Size GB  Type                  Disk name               VMName
-------  ----                  ---------               ------
     20  FixedSize             9002D_SERVER1_disk_1    9002D-SERVER1
      9  DynamicallyExpanding  9021D-TESTVM_disk_1     9021D-MGMTSERVER
     40  FixedSize             _CBBMSERVER
     40  FixedSize             HQVM-PRINTSERVER_C      PRINTSERVER
     20  FixedSize             HQVM-PRINTSERVER_D      PRINTSERVER
      6  DynamicallyExpanding  TEST_disk_1             TEST
     40  FixedSize             _CTest
     15  FixedSize             HQVMAC01_HQVMAC01_C     HQVMAC01
     21  FixedSize             HQVMAC01_D              HQVMAC01
     15  FixedSize             HQVMAC02_C              HQVMAC02
     21  FixedSize             HQVMAC02_D              HQVMAC02



With a little modification to the script you could include additional columns in the output. Let's say that you need CPU count and Memory included in the output:

$vmmserver = Get-VMMServer -ComputerName SCVMMSERVER

$vms =  Get-VM | Sort-object Name | where {$_.HostGroupPath -like "All Hosts*"}

foreach ($vm in $vms)
{
    $disks = $vm | Get-VirtualHardDisk
        foreach($d in $disks) {

        new-object PSObject -prop @{
        VMName = $vm.Name
        Type = $d.VHDType
        "Disk name" = $d.Name
        "Size GB" = [math]::truncate($d.Size / 1GB)
        Memory = $vm.Memory
        "CPU Count" = $vm.CPUCount
        }
        }
}

Note that I have added two rows in the script:

        Memory = $vm.Memory
        "CPU Count" = $vm.CPUCount

The output now looks like this:

Size GB   : 9
CPU Count : 1
Memory    : 1024
Type      : DynamicallyExpanding
Disk name : 9021D-TESTVM_disk_1
VMName    : 9021D-SERVER1

Size GB   : 40
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : _C
VMName    : HQVM-BBM

Size GB   : 40
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : HQVM-PRINTSERVER_C
VMName    : HQVM-PRINTSERVER

Size GB   : 20
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : HQVM-PRINTSERVER_D
VMName    : HQVM-PRINTSERVER

Size GB   : 6
CPU Count : 4
Memory    : 512
Type      : DynamicallyExpanding
Disk name : TEST_disk_1
VMName    : TEST

Size GB   : 40
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : _C
VMName    : Test

Size GB   : 15
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : HQVMAC01_HQVMAC01_C
VMName    : HQVMAC01

Size GB   : 21
CPU Count : 1
Memory    : 1024
Type      : FixedSize
Disk name : HQVMAC01_D
VMName    : HQVMAC01

Size GB   : 15
CPU Count : 1
Memory    : 2048
Type      : FixedSize
Disk name : HQVMAC02_C
VMName    : HQVMAC02

Size GB   : 21
CPU Count : 1
Memory    : 2048
Type      : FixedSize
Disk name : HQVMAC02_D
VMName    : HQVMAC02


This is good, but not exactly the table we expected and it's hard to convert it into a configuration table that we could copy/paste into installation document.

So the solution is to create a function from our script like this:

function Global:Get-VMConfiguration()
{
$vmmserver = Get-VMMServer -ComputerName SCVMMSERVER
$vms =  Get-VM | Sort-object Name | where {$_.HostGroupPath -like "All Hosts\Production*"}

foreach ($vm in $vms)
{
    $disks = $vm | Get-VirtualHardDisk
        foreach($d in $disks) {
        new-object PSObject -prop @{
        VMName = $vm.Name
        Type = $d.VHDType
        "Disk name" = $d.Name
        "Size GB" = [math]::truncate($d.Size / 1GB)
        Memory = $vm.Memory
        "CPU Count" = $vm.CPUCount
        }
        }
}
}


And call that function:

Get-VMConfiguration | Format-Table -Property vmname,memory,"cpu count","disk name",type,"size gb"

This would give you a nicely formatted table that you could paste into a Word document.
Or even better, you could use export-csv function:

Get-VMConfiguration | export-csv -Path c:\Export.csv
Now you can import this comma separated file into Excel and format it as you wish.

Sharepoint 2010 - Cross site collection navigation

Sharepoint 2010 comes with a pretty good navigation out-of-the-box, but there are still some areas where it's not sufficient and that is navigation that crosses site collection boundaries.

If you are designing a large Sharepoint 2010 site architecture you are probably using site collections because they represent security boundary and can also be placed in a separate content database which allows you to physically segment Sharepoint 2010 content across multiple SQL databases.

Fortunately, there is a pretty simple solution to implement cross site collection navigation which makes navigating accross multiple site collections to feel more like having a site structure in a single site collection.

To achieve this we need to create a custom navigation provider in Sharepoint 2010 web application that pulls its data from xml file which represents our site collection structure. This way we can have flat site collection design where all site collections are created in the same URL path level, but also have them hierarchically organised to better represent our organizational structure.

 Here are the steps to implement custom navigation based on xml file.

Copy/paste the following xml in notepad and name it CustomNavigation.sitemap.

<siteMap>
  <siteMapNode title="Home" description="Home" url="/">
    <siteMapNode title="Products" description="Our products" url="/sites/products">
      <siteMapNode title="Hardware" description="Hardware choices" url="/sites/hardware" />
      <siteMapNode title="Software" description="Software choices" url="/sites/software" />
    </siteMapNode>
    <siteMapNode title="Services" description="Services we offer" url="/sites/services">
        <siteMapNode title="Training" description="Training classes" url="/sites/training" />
        <siteMapNode title="Consulting" description="Consulting services" url="/sites/consulting" />
        <siteMapNode title="Support" description="Supports plans" url="/sites/support" />
    </siteMapNode>
  </siteMapNode>
</siteMap>

Now open the Internet Information Services Manager, expand your Sharepoint web application, right-click the _app_bin folder and click Explore.



 Copy the CustomNavigation.sitemap file to this folder.





Now you need to register the new site map navigation provider by modifying the web.config file of your Sharepoint web application.

Open web.config file (it's located one level above from where you pasted CustomNavigation.sitemap) and enter the following line just below the rest of the already registered providers:

    <siteMap defaultProvider="CurrentNavigation" enabled="true">
      <providers>
    ....
    ....

        <add name="CustomXmlContentMapProvider" siteMapFile="_app_bin/CustomNavigation.sitemap" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </providers>
    </siteMap>

Now, maybe you've read on some other blog that you should first copy the following line:

        <add name="SPXmlContentMapProvider" siteMapFile="_app_bin/layouts.sitemap" type="Microsoft.SharePoint.Navigation.SPXmlContentMapProvider, Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />

and then rename it and just modify siteMapFile path. But in my case this did not work very well. Everything was okay on the root portal site, let's say it's http://sps. But on the http://sps/sites/products I would get wrong relative links: http://sps/sites/products/sites/hardware instead of http://sps/sites/hardware. Modifying the xml file with full URL did not work either so when I tried using the System.Web.XmlSiteMapProvider everything worked as it supposed to do. I guess it's because Sharepoint sees http://sps/sites/products as root of the site and not http://sps. The XmlSiteMapProvider does not have this problem.

The next step is to link our custom navigation provider with the top link navigation bar on the Sharepoint site. We need a Sharepoint Designer 2010 to do this.

Browse to the site collection root site where you want to modify navigation and click on Site Actions > Edit in Sharepoint Designer. Keep in mind that you will have to modify all site collections this way to implement consistent navigation.

We will modify the v4.master so that all pages that are based on v4.master will have the same navigation.



Double click on v4.master and click on "Edit file".



Select the "Split" view then right click on the top link navigation bar and click "Zoom to Contents".

Modify the following:

    <SharePoint:AspMenu
      ID="TopNavigationMenuV4"
      Runat="server"
      EnableViewState="false"
      DataSourceID="topSiteMap"
      AccessKey="<%$Resources:wss,navigation_accesskey%>"
      UseSimpleRendering="true"
      UseSeparateCss="false"
      Orientation="Horizontal"
      StaticDisplayLevels="2"
      MaximumDynamicDisplayLevels="1"
      SkipLinkText=""
      CssClass="s4-tn"/>
    <SharePoint:DelegateControl runat="server" ControlId="TopNavigationDataSource" Id="topNavigationDelegate">
        <Template_Controls>
            <asp:SiteMapDataSource
              ShowStartingNode="False"
              SiteMapProvider="SPNavigationProvider"
              id="topSiteMap"
              runat="server"
              StartingNodeUrl="sid:1002"/>
        </Template_Controls>
    </SharePoint:DelegateControl>

To look like this:

    <SharePoint:AspMenu
      ID="TopNavigationMenuV4"
      Runat="server"
      EnableViewState="false"
      DataSourceID="topSiteMap"
      AccessKey="<%$Resources:wss,navigation_accesskey%>"
      UseSimpleRendering="true"
      UseSeparateCss="false"
      Orientation="Horizontal"
      StaticDisplayLevels="1"
      MaximumDynamicDisplayLevels="1"
      SkipLinkText=""
      CssClass="s4-tn"/>
            <asp:SiteMapDataSource
              ShowStartingNode="False"
              SiteMapProvider="CustomXmlContentMapProvider"
              id="topSiteMap"
              runat="server"
              StartingNodeUrl="/"/>

Save the v4.master and open your site in a web browser.

Here are the results:



You'll notice that I've also modified the StaticDisplayLevels property from 2 to 1. This represents to which level Sharepoint will display links from the xml file visible on the top link navigation bar and when will the links be placed in a drop-down menu when you hover over the link. The default setting of 2 will place all links from the xml file visible on the top link bar like this:



So there you go, cross site collection navigation as simple as it can be.