October 27, 2010

The SharePoint 2010 search privileges issue

One of our clients took place the following situation. All users can’t find anything via SharePoint Search center.

The SharePoint Search Service Application was successfully created, The Content was crawled and indexed (index contains about 10 thousands documents).

Symptoms:

  • If you try to find something search results always is empty,
  • Search results is not empty only If you run a browser with the SharePoint Central Administration Pool Account.
  • You can also observe empty scopes in site collection’s options except the People Scope (Url: /_layouts/viewscopes.aspx),

Screenshot: Empty scopes except People Scope

The solution was found here.

This is an insufficient privileges issue. An account which was used for the Search Service Application Pool had no privileges to read user properties from Active Directory. This information need for a security trimming feature of the SharePoint Search.

For resolving the issue you need to add the SharePoint Search Application Pool Account to the Windows Authorization Access group by using the Active Directory Users and Computers snap-in.

And, as usual, I hate it!

October 12, 2010

HOWTO: Create BSC connection with User Profiles via PowerShell

As I described earlier, you can create synchronization connection with User Profiles for Active Directory via PowerShell. And what about the Business Connectivity Services? It’s also possible, of course!

As well as in the previous post, the core of our script is the UserProfileConfigManager class that contains the ConnectionManager property with several methods for adding new connections with user profiles.

Screenshot: UserProfileConfigManager class

The main difference that this time we'll use the AddBusinessCatalogConnection method which has eight parameters (it’s much more than almost any method in the .Net BCL).

public BusinessDataCatalogConnection AddBusinessDataCatalogConnection(
string
displayName,
string
systemName,
string
entityName,
string
entityNamespace,
string
profilePropertyName,
string
filterName,
string
mappedPropertyAttributeName, List<DSMLAttribute> attributeList )

There are simple parameters, except the last: the list of instances of DSMLAttribute (non-documented) class. This class is a simple set of properties, as you can see in the picture below.

Screenshot: DSMLAttribute class

Note: DSML – an acronym that means the Directory Services Markup Language. You can find more information about that on Wikipedia.

Before we begin, I assume that we already have an entity in the BCS model that has at least one method “Read Item” (Specific Finder). Identifier of the entity is a field containing a user account name (it will be mapped to the profile property “Account Name”).

Now, we can code our script. Listing below:

Add-BCSConnection.ps1

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

function Get-First { return @($input)[ 0] }
function Get-Last  { return @($input)[-1] }

function Convert-ToList($inputObject, [System.String]$Type
)
{
   
begin
    {
       
if($type -eq $null -or $type -eq ''
) 
        {
           
$type = [string]
        }
   
       
$list = New-Object System.Collections.Generic.List[$type]
    }
   
   
process { $list.Add($_
) }
   
   
end { return ,$list
 }
}


function Get-SPServiceContext
(
   
[Microsoft.SharePoint.Administration.SPServiceApplication]$profileApp
)
{
   
if($profileApp -eq $null
)
    {
       
# Get first User Profile Service Application
        $profileApp = Get-SPServiceApplication |
 
           
? { $_.TypeName -eq "User Profile Service Application" } |
 
           
Get-First
    }
   
   
return [Microsoft.SharePoint.SPServiceContext]::
GetContext(
       
$profileApp.ServiceApplicationProxyGroup,
 
       
[Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::
Default)
}


function Convert-ToDSMLSyntax([System.String]$typeName
) 
{
   
$DSMLSyntaxType = [Microsoft.Office.Server.UserProfiles.Synchronization.DSMLSyntax, Microsoft.Office.Server.UserProfiles.Synchronization, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]
   
   
if($typeName -match "System.DateTime"
)
    {
       
return $DSMLSyntaxType::
Date
    }
       
   
if($typeName -match "System.Int32"
)
    {
       
return $DSMLSyntaxType::
Integer
    }
   
   
if($typeName -match "System.Byte"
)
    {
       
return $DSMLSyntaxType::
Binary
    }
   
   
if($typeName -match "System.Boolean"
)
    {
       
return $DSMLSyntaxType::
Boolean
    }
       
   
return $DSMLSyntaxType::
String
}


# Global

$DSMLSyntaxType = [Microsoft.Office.Server.UserProfiles.Synchronization.DSMLSyntax, Microsoft.Office.Server.UserProfiles.Synchronization, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]
$DSMLAttributeType = [Microsoft.Office.Server.UserProfiles.Synchronization.DSMLAttribute, Microsoft.Office.Server.UserProfiles.Synchronization, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]

$connectionName = "BCS Connection"
$entityName = "ExternalUserInfo"
$entityNamespace = "ExternalUserInfoDatabase"
$profilePropertyName = "AccountName"

# Main()

$serviceContext = Get-SPServiceContext

# Prepare parameters

$entity = Get-SPBusinessDataCatalogMetadataObject -BdcObjectType Entity
 `
                                                 
-ServiceContext $serviceContext
 `
                                                 
-Name $entityName
 `
                                                 
-Namespace $entityNamespace
   
$entityIdentifier = $entity.Identifiers | Get-First

$specificFinder = $entity.Methods |
 
   
% { $_.MethodInstances } |
    ? { $_.MethodInstanceType -eq "SpecificFinder" } |
 
   
Get-First

$attrList = $specificFinder.ReturnTypeDescriptor.ChildTypeDescriptors |
 
   
%
 {
       
$attribute = New-Object $DSMLAttributeType
       
       
$attribute.ID = $_.
Name
       
$attribute.Name = $_.
Name
       
$attribute.Indexible = $false
        $attribute.SingleValued = $true
        $attribute.Syntax = Convert-ToDSMLSyntax($_.
TypeName)
       
       
$attribute
    } | Convert-ToList -Type $DSMLAttributeType

# Important: Special property for BCS connection!

$attribute = New-Object $DSMLAttributeType
   
$attribute.ID = "SPS-DistinguishedName"
$attribute.Name = "SPS-DistinguishedName"
$attribute.Indexible = 
$true
$attribute
.SingleValued = 
$true
$attribute
.Syntax = Convert-ToDSMLSyntax("System.String")

$attrList.Add($attribute);

# Create BCS Connection

$configMgr = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext)
$connectionMgr = $configMgr.ConnectionManager

if(!$connectionMgr.Contains($connectionName
))
{
   
$connectionMgr.
AddBusinessDataCatalogConnection(
       
<# displayName: #> $connectionName,
 
       
<# systemName: #> $entity.LobSystem.Name,
 
       
<# entityName: #> $entity.Name,
 
       
<# entityNamespace: #> $entity.Namespace,
 
       
<# profilePropertyName: #> $profilePropertyName,
 
       
<# filterName: #> $null,
 
       
<# mappedPropertyAttributeName: #> $entityIdentifier.Name,
 
       
<# attributeList: #> $attrList)
}

Note: If you try to execute code and get error:

Exception calling "AddBusinessDataCatalogConnection" with "8" argument(s): "Could not connect to http://[server-name]:[random-port]/ResourceManagementService/MEX. TCP error co
de 10061: No connection could be made because the target machine actively refused it [ip-address]:[random-port]. "
At C:\[some-path]\Add-BCSConnection.ps1:+     $connectionMgr.AddBusinessDataCatalogConnection <<<< (
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

You must start Forefront Identity Manager Service (via services.msc console) and try again.

That’s all.

And, of course, I hate it!

October 10, 2010

CryptographicException from OWSTIMER.EXE

How often have you seen this window?

CryptographicException

Every day, I believe. So, this is not a bug! This is a feature. Some quotes from our gurus:

Stefan Goßner: The problem here is that the encryption key was created on a specific thread which was impersonated under a specific users account. When the .NET Finalizer processes the encryption key while the timer service shuts down it executed on a different thread which is not impersonated - so the key does not exist and you get an exception like "keyset does not exist".

Paul Andrew: If you see this error message from SharePoint 2010 you can relax, nothing bad is happening. When SharePoint 2010 and Visual Studio 2010 are both installed on the same machine you may see this error every 24 hours. This occurs when the OWSTimer service has a regular process recycle and in the shutdown of the old process an exception is raised. The exception doesn’t interfere with the normal process shutdown and recycle and is only ever seen if you have a JIT debugger installed on the machine. You should never see this error on a production SharePoint 2010 server, because you should not be installing Visual Studio 2010 on those servers. You can safely ignore these exceptions and close the window, or leave it there. You actually cannot debug the process, because it will already have been closed by the time you click the button and start your debugger.

And you can only disable visual studio debugging feature If you want to get rid of this annoying thing.

More information:

And, of course, I hate it!

Powershell: Add Active Directory connection with User Profiles

User Profiles now (SharePoint 2010) is a SharePoint service application that use a Microsoft Forefront Identity 2010 as a underlying tool for SharePoint profiles synchronization.

This is a worst change in SharePoint from previous version. Now, It’s very slow, very hard-configurable and causes huge amount of problems during configure procedures and further work. To verify this you can read these guides: Technet: Configure profile synchronization, Technet: Maintain profile synchronization.

Quote: Incorrect permissions are the most common cause of errors in configuring profile synchronization

Quote: Farm Account must Be a member of the Administrators group on the synchronization server (see the Profile Synchronization Planning worksheet). You can remove this permission after you have configured the User Profile Synchronization service.

Wonderful, isn’t it?

And now We’ll talk about setting up an Active Directory connection with user profiles. You can configure such connection with administrative pages of User Profile Service Application, but these pages are very slow and buggy.

Note: For example, if Active Directory has some granular security settings you may get a timeout error as described here. You can configure timeouts with powershell (here, last paragraph), but that doesn’t help in this case.

We must choose another way: let’s automate the creation of connections with great help of PowerShell gods.

First of all, the core of our script is the UserProfileConfigManager class that contains the ConnectionManager property with several methods for adding new connections with user profiles.

Screenshot: ConnectionManager class

We are interested in the AddActiveDirectoryConnection method, of course. This is a very “simple” method with 8 parameters (!) .

public DirectoryServiceConnection AddActiveDirectoryConnection(
ConnectionType type,
string
displayName,
string
server,
bool
useSSL,
string
accountDomain,
string
accountUsername,
SecureString accountPassword,
List<DirectoryServiceNamingContext> namingContexts,
string
spsClaimProviderTypeValue,
string
spsClaimProviderIdValue
)

And here there is another type DirectoryServiceNamingContext which has importance for us.


Screenshot: DirectoryServiceNamingContext class


Its constructors have large signatures too – 8 and 9 parameters:

public DirectoryServiceNamingContext(
string
distinguishedName,
string
domainName,
bool
isDomain,
Guid objectId,
List<
string
> containersIncluded,
List<
string
> containersExcluded,
List<
string
> preferredDomainControllers,
bool
useOnlyPreferredDomainControllers
)


Thanks God, we have intuitive parameter names and types. So, let’s code our script:


Add-SPActiveDirectoryConnection.ps1:


Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

function Get-SPServiceContext
(
   
[Microsoft.SharePoint.Administration.SPServiceApplication]$profileApp
)
{
   
if($profileApp -eq $null
)
    {
       
# Get first User Profile Service Application
        $profileApp = @(Get-SPServiceApplication |
 
           
? { $_.TypeName -eq "User Profile Service Application" })[0]
    }
   
   
return [Microsoft.SharePoint.SPServiceContext]::
GetContext(
       
$profileApp.ServiceApplicationProxyGroup,
 
       
[Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::
Default)
}


function Convert-ToList($inputObject, [System.String]$Type
)
{
   
begin
    {
       
if($type -eq $null -or $type -eq ''
) 
        {
           
$type = [string]
        }
   
       
$list = New-Object System.Collections.Generic.List[$type]
    }
   
   
process { $list.Add($_
) }
   
   
end
    {
       
return ,$list
    }
}


function Get-DC($domainName
)
{
   
return ("DC=" + $domainName.Replace(".", ",DC="
))
}


# Types

$DirectoryServiceNamingContextType = [Microsoft.Office.Server.UserProfiles.DirectoryServiceNamingContext, Microsoft.Office.Server.UserProfiles, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]

# Globals

$connectionName = "example.com"
$domainName = "example.com"
$accountName = "EXAMPLE\User"
$password = ConvertTo-SecureString "P@ssw0rd" -AsPlainText -Force

$partitions =
 @{
    
"example.com" = @("DC=example,DC=com"
);
     };


# Main()

# Prepare Parameters


$userDomain = $accountName.Substring(0, $accountName.IndexOf("\"))
$userName = $accountName.Substring($accountName.IndexOf("\") + 1)

$dnContexts = $partitions.GetEnumerator() |
     %
 {
    
$domainName = $_.
Key
    
$containers = $_.Value | Convert-ToList
   
    
$partition = [ADSI]("LDAP://" + (Get-DC $domainName
))

    
$partitionId = New-Object Guid($partition.
objectGUID)

    
New-Object $DirectoryServiceNamingContextType
(
       
$partition.distinguishedName,
 
       
$domainName,
 
       
<# isDomain: #> $false,
 
       
<# objectId: #> $partitionId,
 
       
<# containersIncluded: #> $containers,
 
       
<# containersExcluded: #> $null,
 
       
<# preferredDomainControllers: #> $null,
 
       
<# useOnlyPreferredDomainControllers: #> $false
)
    } 
| Convert-ToList -Type 
$DirectoryServiceNamingContextType

$partition
 = [ADSI]("LDAP://CN=Configuration," + (Get-DC $domainName))
$partitionId = New-Object Guid($partition.objectGUID)

$containers = @($partition.distinguishedName) | Convert-ToList

$dnContext = New-Object $DirectoryServiceNamingContextType
(
   
$partition.distinguishedName,
 
   
$domainName,
 
   
<# isDomain: #> $true,
 
   
<# objectId: #> $partitionId,
 
   
<# containersIncluded: #> $containers,
 
   
<# containersExcluded: #> $null,
 
   
<# preferredDomainControllers: #> $null,
 
   
<# useOnlyPreferredDomainControllers: #> $false)

$dnContexts.Add($dnContext)


# Create Active Directory Connection

$serviceContext = Get-SPServiceContext

$configManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager($serviceContext)

if($configManager.ConnectionManager.Contains($connectionName) -eq $false
)
{
   
$configManager.ConnectionManager.
AddActiveDirectoryConnection(
       
[Microsoft.Office.Server.UserProfiles.ConnectionType]::ActiveDirectory,
 
       
$connectionName, $domainName, <# useSSL: #> $false,
 
       
$userDomain, $userName, $password,
 
       
<# namingContexts #> $dnContexts,
 
       
<# spsClaimProviderTypeValue: #> $null,
 
       
<# spsClaimProviderIdValue: #> $null
)
}

else
{
   
Write-Host "Connection '$connectionName' already exist. Delete it before run this script."
}

That’s All.


And, of course, I hate it!