Showing posts with label powershell. Show all posts
Showing posts with label powershell. Show all posts

November 27, 2010

Check Replicating Directory Changes permission via PowerShell

So, as you know, we need ‘Replicating Directory Changes’ permissions for correct work of a User Profiles synchronization (ForeFront Identity Manager).

How to set these permission you can read some other articles:

But how to check that these permission was granted for some user? Of course, we can accomplish this task via PowerShell.

Just change the $userName variable in the script below and run it.

Output like:

User ‘EXAMPLE\User’:
    has a 'Replicating Directory Changes' permission on 'DC=example,DC=local'
    has no a 'Replicating Directory Changes' permission on 'CN=Configuration,DC=example,DC=local'

Check-ADReplicatingChangesPermission.ps1

function Check-ADUserPermission(
   
[System.DirectoryServices.DirectoryEntry]$entry,
 
   
[string]$user,
 
   
[string]$permission
)
{
   
$dse = [ADSI]"LDAP://Rootdse"
    $ext = [ADSI]("LDAP://CN=Extended-Rights," + $dse.
ConfigurationNamingContext)

   
$right = $ext.psbase.Children |
 
       
? { $_.DisplayName -eq $permission
 }
   
   
if($right -ne $null
)
    {
       
$perms = $entry.psbase.ObjectSecurity.Access |
            ? { $_.IdentityReference -eq $user } |
            ? { $_.ObjectType -eq [GUID]$right.RightsGuid.
Value }

       
return ($perms -ne $null
)
    }
   
else
    {
       
Write-Warning "Permission '$permission' not found."
        return $false
    }
}


# Globals

$userName = "EXAMPLE\User"
$replicationPermissionName = "Replicating Directory Changes"

# Main()

$dse = [ADSI]"LDAP://Rootdse"

$entries =
 @(
   
[ADSI]("LDAP://" + $dse.defaultNamingContext),
    [ADSI]("LDAP://" + $dse.configurationNamingContext));

Write-Host "User '$userName': "
foreach($entry in $entries
)
{
   
$result = Check-ADUserPermission $entry $userName $replicationPermissionName
   
   
if($result
)
    {
       
Write-Host "`thas a '$replicationPermissionName' permission on '$($entry.distinguishedName)'"
 `
           
-ForegroundColor Green
    }
   
else
    {
       
Write-Host "`thas no a '$replicationPermissionName' permission on '$($entry.distinguishedName)'"
 `
           
-ForegroundColor Red
    }
}

And, of course, I hate it!

Sincerely yours,

Andrew MossHater.

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

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!