Thursday, July 28, 2011

ADMT Unable to create or merge object

I am performing domain migration and I ran into the following problem error in the migration logs for a user account:

2011-07-29 13:37:35 WRN1:7665 Unable to create or merge object 'CN=Joe Blow,OU=My Users,DC=domain,DC=local' as another instance of ADMT is currently creating or merging the same object.

I had started migrating Joe Blow to the new forest using ADMT but then realised I hadn't started the Password Export Server on the source domain so I hit "Stop" to stop the migration. I then went and started Password Export Server and tried to migrate the account again. This is where I received the above error.

What happened was ADMT recorded in the ADMT migration SQL database that the account is currently locked as its undergoing migration.

I am using SQL Express 2005 on my ADMT 3.2 server on Windows Server 2008 R2. I went and downloaded SQL Management Studio Express 2005 from here:

http://www.microsoft.com/download/en/details.aspx?id=8961

I then found the location in the ADMT database where the account was locked. It is under the table dbo.LockedObjects.



After deleting this record I was able to successfully migrate the user.

Wednesday, July 27, 2011

Making Domain Controllers cover more then one site

When designing Active Directory sites and services, usually you decide which Active Directory site objects you want to place your domain controllers. Sites are usually mapped to physical locations but can also be logical depending on your design.

However, there is a registry key on domain controllers that allows a domain controller to be authoritative for more then one site object in Active Directory. This registry key is known as SiteCoverage.

For more information about this please see:

http://technet.microsoft.com/en-us/library/cc937924.aspx

ADMT is unable to connect to domain controller. 0x80070005

I am performing cross forest migration from 2 AD forests, multiple domains into a new AD forest. When I added one of the domains within a source forest I received the following error:

ADMT is unable to connect to domain controller
\\domaincontroller.sourcedomain.local, in domain sourcedomain.local. Access is denied.
(0x80070005)




Morgan Che posted up multiple causes for this error on the following forum thread:

http://social.technet.microsoft.com/Forums/en/winserverMigration/thread/f0e341f2-d00c-4bf7-925f-250af8530440

I had a different problem to the ones mentioned on the above forum thread. One of my forests was setup with whats called a single labelled domain name. ADMT was having difficulties communicating with all domains within the single labelled forest.

To resolve this on the ADMT server I needed to add a DWORD registry key "AllowSingleLabelDnsDomain" with a decimal value of 1.



ADMT was then able to communicate with all domains in the forest which had a single labelled root domain.

For more information on this registry key please see:

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

Thursday, July 21, 2011

Understanding how "Log On To" works

In this article we will be having a look at how the Log On To list is populated. This is the list in windows XP/2003 where users can select which domain they are logging into from a drop down list.

I decided to blog this as I spent a morning working out how this worked, and there is little documentation on the Internet.

So where does this list come from?

The logon list is stored from a DomainCache registry key located under:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\DomainCache



How does this list populate?

Windows populates the DomainCache registry key from a file called C:\WINDOWS\system32\config\netlogon.ftl.



The DomainCache registry key gets updated from netlogon.ftl as part of the computer boot process and whenever a remote desktop connection is established based on my testing.

Here I added a string record to the DomainCache called CLINTY pointing at a fake domain called clint.local. I then locked my PC, you can see it appear in the list.



If I remote desktop my machine or reboot the PC, it repopulates the DomainCache from netlogon.ftl. Here I remote desktoped my PC, you can see in process monitor it did the repopulation.



When it repopulated the DomainCache from netlogon.ftl, it deleted my CLINTY record.

What populates netlogon.ftl?

The netlogon.ftl file is populated from Active Directory by the netlogon service - I think this occurs when the system boots... but I'm not sure.

Where in Active Directory does netlogon.ftl populate from?

It the defaultNamingContext partition under the System container. It populates from the TrustedDomain object types.



You can also query this information using nltest or a VBScript:

nltest /domain_trusts



Here is a copy of the VB Script used in the above screenshot.

' This code prints the trusts for the specified domain.
' ------ SCRIPT CONFIGURATION ------
strDomain = "wfi.wan"
' ------ END CONFIGURATION ---------

' Trust Direction Constants taken from NTSecAPI.h
set objTrustDirectionHash = CreateObject("Scripting.Dictionary")
objTrustDirectionHash.Add "DIRECTION_DISABLED", 0
objTrustDirectionHash.Add "DIRECTION_INBOUND", 1
objTrustDirectionHash.Add "DIRECTION_OUTBOUND", 2
objTrustDirectionHash.Add "DIRECTION_BIDIRECTIONAL", 3

' Trust Type Constants - taken from NTSecAPI.h
set objTrustTypeHash = CreateObject("Scripting.Dictionary")
objTrustTypeHash.Add "TYPE_DOWNLEVEL", 1
objTrustTypeHash.Add "TYPE_UPLEVEL", 2
objTrustTypeHash.Add "TYPE_MIT", 3
objTrustTypeHash.Add "TYPE_DCE", 4

' Trust Attribute Constants - taken from NTSecAPI.h
set objTrustAttrHash = CreateObject("Scripting.Dictionary")
objTrustAttrHash.Add "ATTRIBUTES_NON_TRANSITIVE", 1
objTrustAttrHash.Add "ATTRIBUTES_UPLEVEL_ONLY", 2
objTrustAttrHash.Add "ATTRIBUTES_QUARANTINED_DOMAIN", 4
objTrustAttrHash.Add "ATTRIBUTES_FOREST_TRANSITIVE", 8
objTrustAttrHash.Add "ATTRIBUTES_CROSS_ORGANIZATION", 16
objTrustAttrHash.Add "ATTRIBUTES_WITHIN_FOREST", 32
objTrustAttrHash.Add "ATTRIBUTES_TREAT_AS_EXTERNAL", 64

set objRootDSE = GetObject("LDAP://" & strDomain & "/RootDSE")
set objTrusts = GetObject("LDAP://cn=System," & _
objRootDSE.Get("defaultNamingContext") )

objTrusts.Filter = Array("trustedDomain")
Wscript.Echo "Trusts for " & strDomain & ":"

for each objTrust in objTrusts

for each strFlag In objTrustDirectionHash.Keys
if objTrustDirectionHash(strFlag) = objTrust.Get("trustDirection") then
strTrustInfo = strTrustInfo & strFlag & " "
end If
next

for each strFlag In objTrustTypeHash.Keys
if objTrustTypeHash(strFlag) = objTrust.Get("trustType") then
strTrustInfo = strTrustInfo & strFlag & " "
end If
next

for each strFlag In objTrustAttrHash.Keys
if objTrustAttrHash(strFlag) = objTrust.Get("trustAttributes") then
strTrustInfo = strTrustInfo & strFlag & " "
end If
next

WScript.Echo " " & objTrust.Get("trustPartner") & " : " & strTrustInfo
strTrustInfo = ""
next

Disable SID Filtering - Access is denied.

I went and attempted to disable SID Filtering over some trust links to prepare for SID History during domain migration using the following command:

netdom trust TrustingDomainName /domain: TrustedDomainName /quarantine:No /userD: domainadministratorAcct /passwordD: domainadminpwd

http://technet.microsoft.com/en-us/library/cc772816.aspx

When doing this I got the following error (click to enlarge):



After research I found the cause. “Network access: Allow anonymous SID/name translation” was set to disabled on the Trusted Domain. This this should be enabled on domain controllers – please see http://technet.microsoft.com/en-us/library/cc728431.aspx.

To disable SID Filtering you must Enable anonymous SID/name translation on your Default Domain Controllers GPO for the Trusted Domain.

I set it to enabled. This policy is located under:

Computer Configuration\Windows Settings\Security Settings\Local Policies\Security Options\



After this the problem was resolved:



Note: Access is denied can also be caused if you use NetBIOS names instead of FQDN's for the domain names.

Monday, July 18, 2011

Microsoft Sync Toy - Perfect for Home Users

Today I stumbled across a fantastic little application called Sync Toy. Perfect for home users who want to backup data to a external drive on a regular basis, between computers or to mapped drive pointing to a cloud provider such as Windows Live.

SyncToy 2.1 is a free application that synchronizes files and folders between locations. Typical uses include sharing files, such as photos, with other computers and creating backup copies of files and folders.

It is so easy to use, I think my mum could do it.

Here I have setup synchronization of My Documents to my Microsoft Windows Live SkyDrive cloud account to ensure my documents are backed up at all times. This is a free service. This also allows me to sync the files down to any new computer I work on.

Z:\ is mapped to windows live cloud account.

Please click to enlarge:



As of this writing the latest version of SyncToy is 2.1. The x86 and x64 version is available from here:

http://www.microsoft.com/download/en/details.aspx?id=15155

Note: If you wish to setup a drive letter mapped to your Windows Live SkyDrive service please see the following link:

http://www.addictivetips.com/microsoft-office/map-local-drive-letter-to-live-skydrive-using-office-2010/

Thursday, July 7, 2011

Bind to AD using Alternative Credentials VBS

Below is a VBS Script by MVP Richard L. Mueller which I found very useful. It shows you how to connect to Active Directory using alternative credentials.

Please view his original post here:
http://www.rlmueller.net/ADOAltCredentials.htm

Option Explicit

Dim objRootDSE, strDNSDomain, adoCommand, adoConnection
Dim strBase, strFilter, strAttributes, strQuery, adoRecordset
Dim strDN, strUser, strPassword, objNS, strServer

Const ADS_SECURE_AUTHENTICATION = &H1
Const ADS_SERVER_BIND = &H200

' Specify a server (Domain Controller).
strServer = "MyServer"

' Specify or prompt for credentials.
strUser = "MyDomain\TestUser"
strPassword = "xyz12345"

' Determine DNS domain name. Use server binding and alternate
' credentials. The value of strDNSDomain can also be hard coded.
Set objNS = GetObject("LDAP:")
Set objRootDSE = objNS.OpenDSObject("LDAP://" & strServer & "/RootDSE", _
strUser, strPassword, _
ADS_SERVER_BIND Or ADS_SECURE_AUTHENTICATION)
strDNSDomain = objRootDSE.Get("defaultNamingContext")

' Use ADO to search Active Directory.
' Use alternate credentials.
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Properties("User ID") = strUser
adoConnection.Properties("Password") = strPassword
adoConnection.Properties("Encrypt Password") = True
adoConnection.Properties("ADSI Flag") = ADS_SERVER_BIND _
Or ADS_SECURE_AUTHENTICATION
adoConnection.Open "Active Directory Provider"
Set adoCommand.ActiveConnection = adoConnection

' Search entire domain. Use server binding.
strBase = "
' Search for all users.
strFilter = "(&(objectCategory=person)(objectClass=user))"

' Comma delimited list of attribute values to retrieve.
strAttributes = "distinguishedName"

' Construct the LDAP query.
strQuery = strBase & ";" & strFilter & ";" _
& strAttributes & ";subtree"

' Run the query.
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False
Set adoRecordset = adoCommand.Execute

' Enumerate the resulting recordset.
Do Until adoRecordset.EOF
' Retrieve values.
strDN = adoRecordset.Fields("distinguishedName").Value
Wscript.Echo strDN
adoRecordset.MoveNext
Loop

' Clean up.
adoRecordset.Close
adoConnection.Close

List all OU's and Sub OU's using VBS

Simple Script to list all OU's and Sub OU's using a VBScript

On Error Resume Next

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE

objCommand.CommandText = _
"SELECT Name FROM 'LDAP://ou=finance,dc=fabrikam,dc=com' WHERE objectCategory='user'"
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst
Do Until objRecordSet.EOF
Wscript.Echo objRecordSet.Fields("Name").Value
objRecordSet.MoveNext
Loop

Wednesday, July 6, 2011

The replication scope could not be set. For more information, see "DNS zone replication in Active Directory

When configuring the DNS zones to replicate to all domains in the forest, instead of all domains just in the current domain the following error was experianced:

"The replication scope could not be set. For more information, see "DNS zone replication in Active Directory" in Help and Support. The error was:

There was a server failure.


To understand where DNS is stored in Active Directory please see:

http://clintboessen.blogspot.com/2010/02/active-directory-dns-zone-locations.html

When trying to connect to the DNS Domain Partition Zone using ADSI Edit (following the above article) the following error was received:

Operation failed. Error code: 0x202b
A referral was returned from the server.

0000202B: RefErr: DSID-03100742, data 0, 1 access points
ref 1 : 'DomainDnsZones.domain.local'




It turned out that the partitions "DomainDNSZones" and "ForestDNSZones" were a lost cause. To fix this you need to perform the following steps:

1. use NTDSUtil to remove the replicas for both ForestDNSZone and DomainDNSZone. Wait for replication. Verify the changes took place then delete each of the partitions.

2. After the deletion has processed to all domain controllers, go into DNS Management and change the Zone to Forest Level/Domain Level. Active Directory will automatically recreate the partition within Active Directory. These new AD application partitions will automatically replicate to all DNS servers. These will then be accessible through ADSI Edit.

It may take over 30 minutes to get to synchronise the DNS zone around - AD is very slow when it comes to DNS.

After this no errors are showing up in the DNS or Active Directory event logs, diagnostics come back clean.

ILM Sample Code PrepareMoveRequest Exchange 2010 Cross Forest Mailbox Moves - Multi Forest

Microsoft Identity Lifecycle Manager Service Pack 1 Feature Pack 1 (ILM 2007 SP1 FP1) can be used to pre-stage the user accounts with the appropriate attributes in a destination forest for cross-forest mailbox moves The out of the box GALSync MA cannot be used since it creates contact object instead of user object required for Online Mailbox Move. Microsoft has provided a sample code extension for the management agents to perform this which can be downloaded from:

http://www.microsoft.com/download/en/details.aspx?id=17741

The ILM sample code demonstrates how to sync source mailbox as Mail Enabled Users (MEU).

The problem with this sample code is was only designed for migration between two forests. My customer wishes to pre-stage user accounts from 2 forests into a new forest meaning I have two source forests! In the OneWaySync.xml file by default we have:

<?xml version="1.0" encoding="utf-8" ?>
<config>
<TargetOU>ou=MaiLboxmoves,DC=targetdom,DC=exchange,DC=contoso,DC=com</TargetOU>
<SourceMAName>Source Forest</SourceMAName>
<TargetMAName>Target Forest</TargetMAName>
</config>


I worked with a Microsoft FIM (Forefront Identity Manager) expert named Tracy Yu and together we made changes to the sample code and recompiled a new DLL to account for multiple source forests.

The file we needed to edit was Microsoft.Exchange.Sample.OneWayGALSync.MVRules.dll. The source code for this file is located under the solution folder under the sample ILM sample code package in a file named Microsoft.Exchange.Sample.OneWayGALSync.MVRules.cs. Here is our new code - in red are any changes made:

// ---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//

// ---------------------------------------------------------------------------

///
/// Example ILM 2007 Provisioning Rule to perform one-way cross-forest GAL synchronization
/// with required prerequisites for mailbox moves from source to target forests
///


using System;
using System.Xml;
using Microsoft.MetadirectoryServices;

namespace Microsoft.Exchange.Sample.OneWayGALSync.MVRules
{
public class MVExtensionObject : IMVSynchronization
{
// define variables for configuration setting
private string targetOU;
private string sourceMAName;
//add by tracy
private string sourceMAName1;
private string targetMAName;

public MVExtensionObject()
{
// No additional constructor logic required
}

void IMVSynchronization.Initialize()
{
// initialize the provisioning rules configuration parameters
// from config file in ILM Extensions directory
XmlDocument xmlConfigFile = new XmlDocument();
xmlConfigFile.Load(Utils.ExtensionsDirectory + "\\OneWaySync.xml");
XmlNode xmlConfig = xmlConfigFile.SelectSingleNode("config");
targetOU = xmlConfig.SelectSingleNode("TargetOU").InnerText.Trim();
sourceMAName = xmlConfig.SelectSingleNode("SourceMAName").InnerText.Trim();
targetMAName = xmlConfig.SelectSingleNode("TargetMAName").InnerText.Trim();
//add by tracy
sourceMAName1 = xmlConfig.SelectSingleNode("SourceMAName1").InnerText.Trim();
}

void IMVSynchronization.Terminate()
{
// No additional termination logic required
}

// For each Mailbox in the Source Forest Provision a connected
// Mail User object in the Target Forest.
void IMVSynchronization.Provision(MVEntry mventry)
{
// ConnectedMA sourceMA = mventry.ConnectedMAs[sourceMAName];
//ConnectedMA targetMA = mventry.ConnectedMAs[targetMAName];
//CSEntry csentry;

//modify by tracy

ConnectedMA targetMA = mventry.ConnectedMAs[targetMAName];
CSEntry csentry;
ConnectedMA sourceMA = null;
ConnectedMACollection MACols = mventry.ConnectedMAs;

foreach(ConnectedMA tmpMA in MACols)
{
if(tmpMA.Name == sourceMAName1)
{
sourceMA = mventry.ConnectedMAs[sourceMAName1];
}
else if (tmpMA.Name == targetMAName)
{
//did nothing
}
else
{
sourceMA = mventry.ConnectedMAs[sourceMAName];
}
}


// if the object has been deleted from Source Forest then delete it
// from Target Forest
if (sourceMA.Connectors.Count == 0)
{
targetMA.Connectors.DeprovisionAll();
return;
}

// This example provisioning rule excludes certain Exchange object types
if (sourceMA.Connectors.Count != 1 ||
!mventry["msExchHomeServerName"].IsPresent ||
!mventry["mailNickName"].IsPresent)
{
return;
}

ReferenceValue targetDN = targetMA.EscapeDNComponent("CN=" + mventry["cn"].Value).Concat(targetOU);

// check for Contacts in target forest that have to be converted to MEUs
for (int index = 0; index < targetMA.Connectors.Count; index++)
{
if (targetMA.Connectors.ByIndex[index].ObjectType.ToLower().Equals("contact"))
{
bool duplicateDN = targetMA.Connectors.ByIndex[index].DN.ToString().ToLower().Equals(targetDN.ToString().ToLower());

targetMA.Connectors.ByIndex[index].Deprovision();
if (duplicateDN)
return;
}
}

if (targetMA.Connectors.Count != 0)
return;

// provision a new AD User Object in the targetOU container
csentry = targetMA.Connectors.StartNewConnector("user");
csentry.DN = targetDN;

// provision the following minimal attributes on the new MailUser object
csentry["samAccountName"].Value = mventry["samAccountName"].Value;
csentry["msexchRecipientTypeDetails"].IntegerValue = 0x80;// MailUser
csentry["userAccountControl"].IntegerValue = 0x202; // ACCOUNTDISABLE | NORMAL_ACCOUNT
csentry["msexchRecipientDisplayType"].IntegerValue = -1073741818; // equivalent to *unsigned* 0xC0000006 i.e. ACL-able, Synced, MailUser
csentry["msExchMasterAccountSID"].Value = mventry["msExchMasterAccountSID"].IsPresent ? mventry["msExchMasterAccountSID"].Value : mventry["objectSID"].Value;
csentry["msExchMailboxGUID"].Value = mventry["msExchMailboxGUID"].Value;
csentry["mailNickname"].Value = mventry["mailNickname"].Value;
csentry["proxyAddresses"].Values = mventry["proxyAddresses"].Values;
csentry["proxyAddresses"].Values.Add("X500:" + mventry["legacyExchangeDN"].Value); // this ensures migrated mail that addresses this user is reply-able in target forest
csentry["msExchVersion"].IntegerValue = 44220983382016; // Set version to E14

csentry.CommitNewConnector();
}

bool IMVSynchronization.ShouldDeleteFromMV(CSEntry csentry, MVEntry mventry)
{
throw new EntryPointNotImplementedException();
}
}
}


Now when we can create two source domains in our OneWaySync.xml file for two source management agents.

<?xml version="1.0" encoding="utf-8" ?>
<config>
<TargetOU>ou=MaiLboxmoves,DC=targetdom,DC=exchange,DC=contoso,DC=com</TargetOU>
<SourceMAName>Source Forest 1</SourceMAName>
<SourceMAName1>Source Forest 2</SourceMAName1>
<TargetMAName>Target Forest</TargetMAName>
</config>


Thanks Tracy!

stopped-error-limit

I was using ILM (Identity Lifecycle Manager) to synchronize users cross forest to prepare for cross-forest mailbox moves using the sample code provided by Microsoft. Please see:

http://www.microsoft.com/download/en/details.aspx?id=17741

I was synchronizing over 5000 users to the new forest. Synchronization was failing with stopped-error-limit.



I found out that MIIS / ILM and FIM only allow up to 5000 objects by default. You can change this with a DWORD in the registry which you create under HKLM\SYSTEM\CurrentControlSet\miisserver\Parameters. This is documented by Microsoft on KB2387673

I set my limit to 10,000 which resolved the problem.