• Home   /  
  • Archive by category "1"

Resource Object Assignment Table For Admt

This post is about my personal journey with a cross-forest migration.

When it comes to account migration there is no way to do so without sIDHistory. It would be really hard to have a smooth migration without.

By using this attribute a end-user most likely won’t experience any impact…..unless you start doing a cleanup of this attribute.

In terms of Exchange users might see something like this

or this

But what’s behind those issues and how could you mitigate this? I was part of a migration, where those issues popped up and I’m going to describe how you could determine possible impact for end-users before it happens.

In my case a migration took place and the team just forgot about to run some tasks to rewrite ACLs on AD and Exchange objects.

Background

In short the migration goal was to migrate from DomainA to DomainB, from one Exchange Org to a new Exchange Org. The approach was using a 3rd Party tool, which means we had to deal with linked mailboxes, which will be converted to mailboxes as soon as the user account was migrated.

Status

After the users were migrated, the next step was to start removing the attribute sIDHistory.

Why removing sIDHîstory and not just leave it there?

Well, if you are already scratching the topic Kerberos Tokensize, you have no choice.

I’m now covering only those topics, which are really hitting your users first and hard. There are 3 parts, which are really important:

  • AD permission like Send-As and Write Personal-Information. Those are most likely used by shared mailboxes or by assistant
  • Mailbox permission like FullAccess
  • Mailboxfolder permission. Here the most used scenario is a delegate scenario, where someone grant another person rights to his calendar or inbox

To describe the scenario I’m going to use the following objects:

Object

Description

Resource.comSource Domain from where the user accounts get migrated
Adatum.comTarget Domain towards user accounts get migrated
UserAUser with linked mailbox. Account is active in source
UserBUser with linked mailbox. Account is active in source
UserCMigrated user with converted mailbox. Account is active in target
Shared01Shared mailbox. Only an object in target exist. UserA, UserB and UserC have Send-As, Write Personal Information and FullAccess

AD permission

Issue:

Now, when you open ADUC you will get all users and the assigned permission. You can see that UserA@resource.com has permissions:

But how does it look when you’re using EMS?

Well, as you can see Exchange reports that the user object in the target domain has the permission UserA@adatum.com:

First thought was to add the target object as a first step and remove the source object in a second step.

Checking with Exchange we can see the object twice:

Checking with ADUC we can distinct the objects:

Using ADUC we can also remove only the source object whereas using Exchange both objects were removed. I’ve tested this again in my lab with Exchange 2010 SP3 RU9 and Exchange 2013 CU8 and here only the specified account was removed and not both.

Anyways the only way to remove only the source object in the existing environment was specifying the SID. But how to check that there is such a SID on an object and how remove/replace user’s permissions in an automated way without any 3rd party product?

Resolution

The final resolution was to read the security descriptor of AD objects and search for SIDs from the source. And this only for the rights Send-As and Write Personal-Information. How can you do that?

First thing you have to know that each extended right is specified in AD and has its own GUID:

To get the GUID I used the following function:

function Get-RightsGUID { param ( $Right ) try {  $Filter = "(&(objectClass=controlAccessRight)(name=$right))"  $root= ([ADSI]'LDAP://RootDse').configurationNamingContext  $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"LDAP://$root")  $searcher.filter = "$Filter"  $searcher.pagesize = 1000  $results = $searcher.findone()  $results.Properties.rightsguid } catch {  $_.ErrorMessage } }

Let’s find the GUID for the right ‘Send-As’

Get-RightsGUID Send-As

As we have the GUID we now need to read the security descriptor(SD) and parse for the GUID of the right.

How can you read the SD? Well, there is a simple trick….using PS and make use of the property psbase.ObjectSecurityof the PS object itself

([ADSI](([ADSISearcher]"(samaccountname=Shared01)").FindOne().Path)).psbase.ObjectSecurity

We are looking for the CodeProperty SDDL, but we need to do some formating…

([ADSI](([ADSISearcher]"(samaccountname=Shared01)").FindOne().Path)).psbase.ObjectSecurity.Sddl.split("(") | %{$_.Trim(")")}

Security descriptor have a defined format in ACE strings, please read more about it here. As we have the SD and the GUID for the right, we can easily parse the output:

$allSIDs=([ADSI](([ADSISearcher]"(samaccountname=Shared01)").FindOne().Path)).psbase.ObjectSecurity.Sddl.split("(") | %{$_.Trim(")")} $sids = $allSIDs -match $right | select @{l="SID";e={$_.Split(";")[5]}} | ?{$_ -match "S-1"} $sids

Putting all together you can create another function:

function Get-SIDforRight { param ( $Right, $Samaccountname )  $allSIDs=([ADSI](([ADSISearcher]"(samaccountname=$Samaccountname)").FindOne().Path)).psbase.ObjectSecurity.Sddl.split("(") | %{$_.Trim(")")}  $sids = $allSIDs -match $right | select @{l="SID";e={$_.Split(";")[5]}} | ?{$_ -match "S-1"}  $sids }

With both functions you can parse the SD of object for a specific right like Send-As, which doesn’t match the DomainSID:

$root= [ADSI]'LDAP://RootDse' $domain = New-Object System.DirectoryServices.DirectoryEntry $domainsid= (New-Object System.Security.Principal.SecurityIdentifier($domain.objectSid[0],0)).value $domainsid Get-SIDforRight -Samaccountname shared01 -Right $guid | ?{$_ -notmatch $domainsid}

Now that you have the SID from the source and you can go ahead and remove them.

Mailbox permission

A similar approach was taken with mailbox permission. Here you have the same behavior when you query Exchange for permissions

First idea was to use the returned property SecurityIdentifier for a user

Here we got a value of S-1-5-21-2660594480-276202035-994542512-1175 from Exchange for UserB. But is this really correct? How can we check this?

Exchange writes back to AD the mailbox permission. You can find the values in the attribute msExchMailboxSecurityDescriptor, which has the same format as a security descriptor. In order to read this attribute like a SD you need to convert the value

#get the user object $User=([ADSISearcher]"(mailnickname=shared01)").FindOne().Properties #get the value into a variable with type bytearray [byte[]]$DaclByte = $User.msexchmailboxsecuritydescriptor[0] #create a new object of ActiveDirectorySecurity class $adDACL = new-object System.DirectoryServices.ActiveDirectorySecurity #finally set the created object with the value of the bytearray $adDACL.SetSecurityDescriptorBinaryForm($DaclByte) #return object $adDACL.Sddl.Split("(") | %{$_.Trim(")")}

Now that you have the native SD you need to translate it somehow. Again also this SD has a format. It has a semicolon as delimiter and on the third rank you can find the right. In this case it’s “CC”, which means “Create Child” and in terms of Exchange “FullAccess”. Double-check the SIDs we have here in this SD

You can see that both objects(source and target) of users UserA and UserB have FullAccess. Exchange shows only Adatum*, while you can see in the SD that both SIDs are present. Below I queried the attributes from AD and in the SIDHistory attribute the SID of the source is shown.

Looking at the CmdLet Add-Mailboxpermission we can set 6 different rights, which ends in the following mapping:

# WD = ChangePermission
# WO = ChangeOwner
# SD = DeleteItem
# LC = ExternalAccount
# CC = FullAccess
# RC = ReadPermission
# SendAS is an AD-permission

When you put everything together you have a function, which is retrieving the attribute msExchMailboxSecurityDescriptor, extract the SIDs, which are not match the DomainSID and the translated rights. I called it Check-SIDfromMBSecurity

function Check-SIDfromMBSecurity { param(  [parameter( ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=0)]  [string]$Alias ) process { # retrieve local domain SID $root= [ADSI]'LDAP://RootDse' $domain = New-Object System.DirectoryServices.DirectoryEntry $domainsid= (New-Object System.Security.Principal.SecurityIdentifier($domain.objectSid[0],0)).value #$domainsid try { # retrieve SDDL from AD attribute sexchmailboxsecuritydescriptor function Get-msExchMailboxSecurityDescriptor { param( $Samaccount )  $User=([ADSISearcher]"(mailnickname=$Samaccount)").FindOne().Properties  [byte[]]$DaclByte = $User.msexchmailboxsecuritydescriptor[0]  $adDACL = new-object System.DirectoryServices.ActiveDirectorySecurity  $adDACL.SetSecurityDescriptorBinaryForm($DaclByte)  $adDACL } # convert SDDL to readable form from attribute msexchmailboxsecuritydescriptor function Get-SIDfromDescriptor { param( $Samaccount )  $temp = (Get-msExchMailboxSecurityDescriptor $Samaccount).Sddl.Split("(") | %{$_.Trim(")")} | select @{l='RightsRaw';e={$_.Split(';')[2]}},@{l='SID';e={$_.Split(';')[5]}},@{l='ACEType';e={$_.Split(';')[0] | ?{($_ -eq 'D') -or ($_ -eq 'A')}}}  $temp } # map permission to readable Exchange permission function TranslateSDDL { param ( [string]$SDDL )  $temp = $SDDL -split '(?<=\G\w{2})(?=\w{2})'  # WD = ChangePermission  # WO = ChangeOwner  # SD = DeleteItem  # LC = ExternalAccount  # CC = FullAccess  # RC = ReadPermission  # SendAS is an AD-permission  [string]$TranslatedRights = ""  ForEach ($right in $temp) {   switch ($right) {    "WD" {$TranslatedRights += "ChangePermission,"}    "WO" {$TranslatedRights += "ChangeOwner,"}    "SD" {$TranslatedRights += "DeleteItem,"}    "LC" {$TranslatedRights += "ExternalAccount,"}    "CC" {$TranslatedRights += "FullAccess,"}    "RC" {$TranslatedRights += "ReadPermission,"}    default {"Unkown $($right)"}   }  } $TranslatedRights.Trim(',') } # resolve a user for a given SID function Get-UserForSID { param (  [parameter( Mandatory=$true, Position=0)]  [String]$SID ) try {  $objSID = New-Object System.Security.Principal.SecurityIdentifier("$SID")  $objUser = $objSID.Translate( [System.Security.Principal.NTAccount])  $objUser.Value catch {  $_.Exception.Message } } $oldSIDs=Get-SIDfromDescriptor $alias | ?{($_.SID -notmatch $domainsid) -and ($_.SID -match 'S-1')} If ($oldSIDs) {  $objcol = @()  ForEach ($oldSID in $oldSIDs){   $data = new-object PSObject   $data | add-member -type NoteProperty -Name Mailbox -Value $alias   $data | add-member -type NoteProperty -Name OldSID -Value $oldSID.SID   $data | add-member -type NoteProperty -Name RightsRaw -Value $oldSID.RightsRaw   $data | add-member -type NoteProperty -Name RightsTranslated -Value $(TranslateSDDL $oldSID.RightsRaw)   $data | add-member -type NoteProperty -Name UserID -Value $((Get-UserForSID $oldSID.SID).Split('\')[1])   $data | add-member -type NoteProperty -Name Domain -Value $((Get-UserForSID $oldSID.SID).Split('\')[0])   $data | add-member -type NoteProperty -Name ACE -Value $oldSID.ACEType   $objcol += $data  } $objcol } } Catch {  $Error[0].Exception } } }

Of course the returned domain in those cases here is the local domain Adatum. I will cover this later, but for now I can tell you it’s because the SID could be found in the attribute SIDHistory.

Anyways this gives you now the posibility to do a cleanup, which means either to replace or remove permission entries.

In my case we first exported all the permissions to a file in order to have a backup and an idea how many mailboxes were affected.Then we first removed the permission and added them again (of course with the correct object!).

Mailboxfolderpermission

This part was really the hardest one as there is no AD attribute, which you could parse to get the raw SD to look for non-local domain SID entries. So far the only way to get the property PR_NT_Security_Descriptor property of a mailboxfolder in a human readable format is MFCMAPI and MrMAPI (which plays a major role later!”).

As an example I made UserB as delegate for UserA. So UserB has access on Calendar and Inbox

Looks okay, but when you look at the property PR_NT_Security_Descriptor you can see that Exchange stamped the SID from the source on the folder

Now the question was how to get all the affected mailboxes and folders? When you have a cross-forest migration and you’re working with linked mailbox, you will end in the situation that you will have folders with SIDs from the source domain, when the users were added to the folder before they were migrated, means they were a linked mailbox. Whereas when users with normal mailbox were added the domain local SID is used and stamped on the mailbox. To get this proven we will use UserC, which was migrated the same way as the other users, but the difference to UserB is that he was added as a delegate after he was migrated. So at the time he was added his mailbox was not a linked mailbox. The SD looks like this now:

Security Descriptor:

Security Info: 0x0

Security Version: 0x0003 = SECURITY_DESCRIPTOR_TRANSFER_VERSION

Descriptor:

Account: ADATUM\userb

SID: S-1-5-21-2660594480-276202035-994542512-1175

Access Type: 0x00000000 = ACCESS_ALLOWED_ACE_TYPE

Access Flags: 0x00000002 = CONTAINER_INHERIT_ACE

Access Mask: 0x001208AB = fsdrightListContents | fsdrightCreateItem | fsdrightReadProperty | fsdrightExecute | fsdrightReadAttributes | fsdrightViewItem | fsdrightReadControl | fsdrightSynchronize

Account: ADATUM\userb

SID: S-1-5-21-2660594480-276202035-994542512-1175

Access Type: 0x00000001 = ACCESS_DENIED_ACE_TYPE

Access Flags: 0x00000002 = CONTAINER_INHERIT_ACE

Access Mask: 0x000D4114 = fsdrightCreateContainer | fsdrightWriteProperty | fsdrightWriteAttributes | fsdrightOwner | fsdrightWriteSD | fsdrightDelete | fsdrightWriteOwner

Account: ADATUM\userb

SID: S-1-5-21-2660594480-276202035-994542512-1175

Access Type: 0x00000000 = ACCESS_ALLOWED_ACE_TYPE

Access Flags: 0x00000009 = OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE

Access Mask: 0x001F0FBF = fsdrightListContents | fsdrightCreateItem | fsdrightCreateContainer | fsdrightReadProperty | fsdrightWriteProperty | fsdrightExecute | fsdrightReadAttributes | fsdrightWriteAttributes | fsdrightViewItem | fsdrightWriteSD | fsdrightDelete | fsdrightWriteOwner | fsdrightReadControl | fsdrightSynchronize | 0x600

Account: ADATUM\userc

SID: S-1-5-21-398472632-1282482148-99536377-1236

Access Type: 0x00000000 = ACCESS_ALLOWED_ACE_TYPE

Access Flags: 0x00000002 = CONTAINER_INHERIT_ACE

Access Mask: 0x001208AB = fsdrightListContents | fsdrightCreateItem | fsdrightReadProperty | fsdrightExecute | fsdrightReadAttributes | fsdrightViewItem | fsdrightReadControl | fsdrightSynchronize

Account: ADATUM\userc

SID: S-1-5-21-398472632-1282482148-99536377-1236

Access Type: 0x00000001 = ACCESS_DENIED_ACE_TYPE

Access Flags: 0x00000002 = CONTAINER_INHERIT_ACE

Access Mask: 0x000D4114 = fsdrightCreateContainer | fsdrightWriteProperty | fsdrightWriteAttributes | fsdrightOwner | fsdrightWriteSD | fsdrightDelete | fsdrightWriteOwner

Account: ADATUM\userc

SID: S-1-5-21-398472632-1282482148-99536377-1236

Access Type: 0x00000000 = ACCESS_ALLOWED_ACE_TYPE

Access Flags: 0x00000009 = OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE

Access Mask: 0x001F0FBF = fsdrightListContents | fsdrightCreateItem | fsdrightCreateContainer | fsdrightReadProperty | fsdrightWriteProperty | fsdrightExecute | fsdrightReadAttributes | fsdrightWriteAttributes | fsdrightViewItem | fsdrightWriteSD | fsdrightDelete | fsdrightWriteOwner | fsdrightReadControl | fsdrightSynchronize | 0x600

For UserC the SID S-1-5-21-398472632-1282482148-99536377-1236 is reported and not S-1-5-21-2660594480-276202035-994542512-1179 from the source domain

The next try was to use EWS and query the permission

Okay, the SID for UserC is correct, but the one for UserB is incorrect. The problem is that EWS reports only what Exchange resolves. And even less. When you have invalid entries like a disabled user it won’t be return. Orphaned entries like ‘NT User:*****’ won’t be returned. Those will be listed only when you use the CmdLet Get-MailboxFolderPermission. So how to overcome this issue and get to know which mailbox might be affected?

At this point I really would like to thank MCM Danijel Klaric, who came up with the idea to use MrMapi.exe to parse the property PR_NT_Security_Descriptor!

I wrote a script to get all the permission and some other data from the mailboxes using EWS, but I couldn’t find a way to read the property 0x0E27 on a folder. I was able to get the binary blob, but couldn’t find a way to convert it into a readable format. By using MrMapi.exe I was able to do so and in the end I had a combination of a script using EWS and MrMapi.

I will publish this script in the near future, but for now I can show you the difference. Pure EWS looks like this

Exchange CmdLet reports this

I disabled UserC’s mailbox. EWS doesn’t report it, but Exchange CmdLet does. And now using MrMapi.exe and parsing the property

The script returns some values:

Property

Description

Mailboxthe mailbox, which was parsed
Userif resolvable the user
SIDinSDthe SID stamped on the folder
Foldernamefoldername
FolderTypetype of the folder
Folderpathpath of folder within the mailbox
EwsIDEWS value of the folderID
StoreIDto storeID converted EwsID. So you can use this ID to access the folder using Exchange CmdLets

Main part for the script was taken from Glen Scales. I modified it and wrapped a script around it so it accepts some parameters. What is really efficient is that I made it mutli-threaded, but more about it in the future.

Now I was able to identify mailboxes and the solution was almost the same as with the mailbox permission….removing and to add them again with the correct object.

First get a list of affected mailboxes and save the output into a file

$root= [ADSI]'LDAP://RootDse' $domain = New-Object System.DirectoryServices.DirectoryEntry $domainsid= (New-Object System.Security.Principal.SecurityIdentifier($domain.objectSid[0],0)).value Get-Mailbox -Database ex02-db01 | .\Get-MailboxFolderPermissionEWS.ps1 -Impersonate -MultiThread -UseMrMapi -MrMapi C:\Scripts\mrmapi.exe | ?{($_.User -notmatch 'ANONYMOUSLOGON')-and ($_.User -ne $null) -and ($_.User -notmatch 'Everyone') -and ($_.SIDinSD -notmatch $domainsid)} | | Export-Csv -NoTypeInformation -Path .\ForeignSIDs.csv

Second is to retrieve for all affected mailboxes the current permission and also write them into a file(just to be on the safe side as a backup and easier clean-up). Therefore I needed a small function to read the objects from the file and query the permissions with Exchange CmdLets

function Get-FolderPermission { param( [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$true, Position=0)] [Alias('Mailbox')] [string]$MB, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=1)] [Alias('StoreID')] [string]$Folder, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=2)] [Alias('User')] [string]$ID ) process { If (!(Get-Command Get-MailboxFolderStatistics -ErrorAction silentlycontinue)){ Write-Warning "No CmdLets found!" Exit } $Error.Clear() $objcol = @() $Perms = Get-MailboxFolderPermission $($MB+":"+$Folder) -User $ID If (!($Error.count -gt 0)) {   $data = new-object PSObject   $data | add-member -type NoteProperty -Name Mailbox -Value $MB   $data | add-member -type NoteProperty -Name User -Value $ID   $data | add-member -type NoteProperty -Name FolderName -Value $Perms.FolderName   $data | add-member -type NoteProperty -Name AccessRights -Value $($Perms.AccessRights -join ',')   $data | add-member -type NoteProperty -Name FolderID -Value $Folder   $data | add-member -type NoteProperty -Name Valid -Value 'True'   $objcol += $data } Else {  $data = new-object PSObject  $data | add-member -type NoteProperty -Name Mailbox -Value $MB  $data | add-member -type NoteProperty -Name User -Value $ID  $data | add-member -type NoteProperty -Name FolderName -Value 'n/a'  $data | add-member -type NoteProperty -Name AccessRights -Value 'n/a'  $data | add-member -type NoteProperty -Name FolderID -Value $Folder  $data | add-member -type NoteProperty -Name Valid -Value 'False'  $objcol += $data } $objcol } }#import from previous file $affected = Import-Csv .\ForeignSIDs.csv #pipe the import into the function and write it into a file $affected | Get-FolderPermission | Export-Csv -NoTypeInformation -Path .\ForeignSIDs_Perm_Backup.csv

Now you have a final file with all the necessary data. Import the last file and then remove and add the permission with the following function

function Clean-FolderPermission { Param ( [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$true, Position=0)] [Alias('Mailbox')] [string]$MB, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=1)] [Alias('FolderID')] [string]$Folder, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=2)] [Alias('AccessRights')] [string]$Rights, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=3)] [Alias('User')] [string]$UserID, [parameter( ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=4)] [Alias('Valid')] [string]$Status, [bool]$WhatIf=$True ) If ($WhatIf){ $Expr= "Remove-MailboxFolderPermission $($MB+":"+$Folder) -User $UserID -WhatIf" Invoke-Expression $Expr If ($Status -eq 'True'){ $Expr= "Add-MailboxFolderPermission $($MB+":"+$Folder) -User $UserID -AccessRights $Rights -WhatIf" Invoke-Expression $Expr } } Else { Remove-MailboxFolderPermission $($MB+":"+$Folder) -User $UserID -Confirm:$false If ($Status -eq 'True'){ $Expr= "Add-MailboxFolderPermission $($MB+":"+$Folder) -User $UserID -AccessRights $Rights" Invoke-Expression $Expr } } }#import the backup of permissions $final = Import-Csv .\ForeignSIDs_Perm_Backup.csv #pipe the import into the function to clean $final | %{$_ | Clean-FolderPermission -WhatIf 0}

Now let’s check the Calendar of UserA and the SIDs stamped on this folder

Get-Mailbox usera | .\Get-MailboxFolderPermissionEWS.ps1 -Impersonate -CalendarOnly -UseMrMapi -MrMapi C:\Scripts\mrmapi.exe

Note: Those step you will do only after the account migration took place!

The question why?

Why are we not able to see the correct user as soon as the attribute sIDHistory is used?

This behavior is by design. When you read the following article it’s getting more clear:

Implementation

LookupAccountSid will call into LsaLookupSids with a single SID to resolve. So LsaLookupSids is covered in this section.

LSA on the computer that the call is sent to (using the LSA RPC interface) will resolve the SIDs it can map and send on the remaining unresolved SIDs to a domain controller in the primary domain. The domain controller will resolve additional SIDs to account names from the local database, including SIDs found in SidHistory on a global catalog.

If SIDs cannot be resolved there, the domain controller will send remaining SIDs to domain controllers in a trusted domain where the domain part of the SID matches the trust information.

So either way calling the function LsaLookupSids or LookupAccountSid the domain controller will always return the local object and this is what you will see.

Challenges

Limitations

As mentioned we used a 3rd party tool for the migration. We stumbled across some limitations/issues:

Limitation #1

The first is that it seems to be a known issue related to folders with foldertype IPF.Appointment means all Calendar. When you have users with exact this permission “LimitedDetails” or in Outlook “Free/Busy time.subject,location”

In this case the permission were set to “AvailabilityOnly” or in Outlook “Free/Busy time”

Of course this shouldn’t be a big deal…..but not when you have a whole bunch of rooms, which are restricted with a BookinPolicy. Guess what the default permission Exchange is granting those users? Yes, “LimitedDetails” or in Outlook “Free/Busy time.subject,location”.

Limitation #2

Another limitation is that the toll stopped processing a folder as soon as there was a SID, which couldn’t be resolved. In this case all entries of this folder were skipped and the stamped SID was not replaced.

Limitation #3

This limitation gave us some headaches. It took a while until we figured out what’s going on. We got for some mailboxes an error 404 (the tool uses EWS). Some network and Fiddler traces revealed that the tool tried to connect to the mailbox straight using the mailbox server(we have split roles). In the end the problem was really easy to solve:

The attribute msExchHomeServerName was not matching with the name of the server where the database was active. This is also by design, when you have a DAG with multiple copies. Nevertheless the tool just throw an error ‘Could not find client access server for database xxx. Use server xxx.’

I changed the attribute to the matching servername and the tool was able to process the mailboxes.

Issues

Issue #1

I modified on many mailboxes the mailboxpermissions like FullAccess. Afterwards we received some calls from users. They got prompted for credentials when they tried to access shared mailboxes. We checked everything on client-side and also the permission on the shared mailbox. In the end I compared the attribute msExchMailboxSecurityDescriptor with the output from the CmdLet Get-MailboxPermission. Use the following function to do so:

function Check-SecurityDescriptor { param(  [parameter( ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=0)]  [string]$Alias )process { # retrieve SDDL from AD attribute sexchmailboxsecuritydescriptor function Get-msExchMailboxSecurityDescriptor { param( $MailNick )  $User=([ADSISearcher]"(mailnickname=$MailNick)").FindOne().Properties  [byte[]]$DaclByte = $User.msexchmailboxsecuritydescriptor[0]  $adDACL = new-object System.DirectoryServices.ActiveDirectorySecurity  $adDACL.SetSecurityDescriptorBinaryForm($DaclByte)  $adDACL }# convert SDDL to readable form from attribute msexchmailboxsecuritydescriptor function Get-SIDfromDescriptor { param( $MailNick )  $temp = (Get-msExchMailboxSecurityDescriptor $MailNick).Sddl.Split("(") | %{$_.Trim(")")} | select @{l='RightsRaw';e={$_.Split(';')[2]}},@{l='SID';e={$_.Split(';')[5]}},@{l='ACEType';e={$_.Split(';')[0] | ?{($_ -eq 'D') -or ($_ -eq 'A')}}}  $temp } #get SD [array]$SD = Get-SIDfromDescriptor -MailNick $Alias | ?{$_.SID -like 'S-*'} | %{$_.SID} #get MB permission $MBPerms = Get-MailboxPermission $Alias | ?{$_.IsInherited -like 'False'} $objcol = @() ForEach ($SID in $MBPerms) {  If ($SID.User -notlike 'NT AUTHORITY\SELF') {   $data = new-object PSObject   $data | add-member -type NoteProperty -Name Mailbox -Value $Alias   $data | add-member -type NoteProperty -Name User -Value $SID.User   $data | add-member -type NoteProperty -Name AccessRights -Value $($SID.AccessRights -join ',')   $data | add-member -type NoteProperty -Name SID -Value $SID.User.SecurityIdentifier.Value   #Write-Host "Checking " $SID.User   #$SID.User.SecurityIdentifier.Value   If ($SD -contains $SID.User.SecurityIdentifier.Value) {    #Write-Host -fore green "$($SID.User) is in SD!"    $data | add-member -type NoteProperty -Name Valid -Value 'True'   }   Else {    #Write-Host -fore red "$($SID.User) is NOT in SD!"    $data | add-member -type NoteProperty -Name Valid -Value 'False'   }  }  $objcol += $data } $objcol } }

This is how it looks like

Issue #2

I stumbled across some mailboxes where I got an error that the ACE table could not be updated. This error came when I tried to fix folderpermissions. The only way was to use Outlook to get those fixed. But this was really rare.

Conclusion

When you’re doing a cross-forest migration with 3rd party tolls, I strongly encourage you to use them to get the job done! I wrote this article not in order to have a replacement for those tools. But also those tools have their limitations or known issues. With this article at least you can have a plan B when something went wrong. In my case some parts of the infrastructure was already removed and couldn’t be recovered or it made no sense to rebuild it.

I hope this helps someone when you run into a similar situation.

Update:

The script I used to collect the folder-level permissions and the SIDs is now available for download.

Please have a look at the post here and direct link to download is here.

Like this:

LikeLoading...

Related

This entry was posted in Migration and tagged #MSExchange, Function, LDAP, Migration, sIDHistory by Ingo Gegenwarth. Bookmark the permalink.
5/5(3)

5/53

In this first blog post, I’ll walk you through to migrate Active Directory objects (users, groups, and workstations or member servers) between two domains in the same forest (Intraforest) using Active Directory Migration Tool (ADMT) 3.2.

ADMT allows you to migrate objects (including users, groups, computers, profiles, service and managed service accounts) with the help of ADMT console, command line, and VBScript. However, in this post, I’ll focus only on ADMT console and command line.

Intraforest Active Directory Domain Object Migration

When you migrate objects between domains in the same forest, the migrated objects no longer exist in source domain except computer accounts which are copied. Following table list some behaviors during the migration process.

Table 1: Intraforest migration behavior

Include File

When you have limited number of objects to migrate, you can directly specify them in a command line or in ADMT console. However, when you migrate a large number of objects, it is more efficient and less time consuming to specify them in an include file. Include file is a text file in which you place each object on a separate line. You can then provide the path of that file in ADMT console or command line during the migration process.

The following table list fields of an include file with their explanation.

Table 2: Include file fields

It is mandatory to specify source name of an object in include file while rest of the fields are optional. You can specify optional fields in any combination and in any order. I have listed below few examples to make things more clear.

SourceName

John

SourceName,TargetRDN

John, CN=johnny

SourceName,TargetRDN,TargetSAM

John, CN=johnny, johnnym

SourceName,TargetRDN,TargetSAM,TargetUPN

John, CN=johnny, johnnym, johnm@yourdomain.com

Preparing for AD Objects Migration

Before you proceed with migration process, cross-check the following requirements.

  1. Identify the source, target domain and the organizational unit (OU) where you will place migrated objects.
  2. Create an assignment table and document the domain objects that you are migrating with their source and target locations.
  3. ADMT doesn’t have any built-in migration test options. You should develop a test plan separately and test each object during and after they are migrated to the target domain. Identify and correct any problems to make sure that the objects once migrated can access resources based on their group membership and credentials.
  4. The migration process is non-reversible and you cannot roll back changes. Once objects are migrated, the only way is to remigrate them from target domain back to the source domain. You should have a rollback plan and the method you will use to remigrate objects.
  5. Inform all affected users beforehand about accounts migration plan and its schedule so that they are aware of the impact of the migration.
  6. Download and install the latest version of Active Directory Migration Tool (ADMT) 3.2 in the target domain.

Lab Topology Overview

I have three domains in my forest. Root domain, child domain, and tree domain. Each domain has a single domain controller and they are running on Windows Server 2016. Default two-way trust is already created between domains since they are part of a single forest. The full topology is shown in the following figure.

Figure 1: Lab topology overview

In this article, I’ll show you to migrate objects from child domain (child.yourdomain.com) to parent domain (yourdomain.com). The process is same if you migrate between tree domain (ourtreedomain.com) and child domain or vice versa because there is a default transitive trust between them.

Migrating Objects from Child Domain to Parent Domain Using ADMT Snap-in

Migrating Limited Users

Step 1. Log in with ADMT migration account on computer in target or parent domain where ADMT is installed

Step 2. Right-click Active Directory Migration tool and then click User Account Migration Wizard

Figure 2: ADMT Snap-in

Step 3. Click Next

Step 4. Provide or select NetBIOS or DNS name of the source and the target domains. Provide or select the name of domain controller of source and target domains (or select Any domain controller) and click Next

Figure 4: Source and target domains selection

Step 5. Click ‘Select users from domain’ radio button and then click Next

Figure 5: User selection method

Step 6. Click Browse and add desired user(s) you would like to migrate

Figure 6: Adding users

Step 7. Click Next

Figure 7: Adding users

Step 8. Click Browse to choose the target OU for migrating users

Figure 8: Target OU selection

Step 9. Click Next

Figure 9: Target OU selection

Step 10. Check both Translate roaming profiles, and Update user rights. Ignore any warnings and click Next

Figure 10: User migration options

Step 11. Click ‘Do not migrate source object if a conflict is detected in the target domain’ radio button and click Next

Figure 11: User accounts conflict management

Step 12. Click Finish

Figure 12: Completing the user migration wizard

Step 13. Wait for the wizard to complete and look for any errors. Click Close

Figure 13: User migration progress

Step 14. Open Active Directory Users and Computers snap-in and verify the user account in target OU.

Migrating Large Number of Users Using Include File

Steps 1,2, 3, 4 are similar to single user migration wizard. However, proceed as follow after step 4.

– Click ‘Read object from an include file’ radio button and click Next

Figure 14: User selection method

– Click Browse and choose the path of include file from local hard drive of your computer

Figure 15: Providing include file path

When you are done with above steps, proceed with step 8 of single user migration wizard and follow it till the end.

Step 1. Log in with ADMT migration account on computer in target or parent domain where ADMT is installed

Step 2. In ADMT snap-in, right-click Active Directory Migration Tool and then click Group Account Migration Wizard

Figure 16: ADMT snap-in

Step 3. Click Next

Figure 17: Group account migration wizard

Step 4. Provide or select NetBIOS or DNS name of the source and target domains. Provide or select the name of domain controller of source and target domains (or select Any domain controller) and click Next

Figure 18: Source and target domains selection

Step 5. Click ‘Select groups from domain’ radio button and click Next

Figure 19: Group selection method

Step 6. Add the desired group(s) you would like to migrate and click Next

Figure 20: Adding groups

Step 7. Click Browse and choose the target OU for migrating group(s). When you are done click Next

Figure 21: Choosing target OU

Step 8. Click Next and ignore any warnings if they appear

Figure 22: Group options

Step 9. Click ‘Do not migrate source object if a conflict is detected in the target domain’ radio button and click Next

Figure 23: Group account conflict management

Step 10. Click Finish

Figure 24: Completing the group account migration wizard

Step 11. Wait for a wizard to complete and look for any errors. Click Close

Figure 25: Group migration progress

Step 12. Open Active Directory Users and Computers snap-in and verify the group account in target OU.

Migrating Large Number of Groups Using Include File

When you are migrating multiple groups using an include file, first four steps are same from single group migration wizard. From step 5, proceed as follow.

– Click ‘Read objects from an include file’ radio button and click Next

Figure 26: Group selection method

– Click Browse and choose the path of include file from your local hard drive. When you are done click Next

Figure 27: Providing include file path

When you are done with above steps, proceed to step 7 of single group migration wizard and follow it till the end.

Migrating Limited Workstations or Member Servers

Step 1. Log in with ADMT migration account on computer in target or parent domain where ADMT is installed

Step 2. In ADMT snap-in, right-click Active Directory Migration Tool and then click Computer Migration Wizard

Figure 28: ADMT snap-in

Step 3. Click Next

Figure 29: Computer migration wizard

Step 4. Provide or select NetBIOS or DNS name of the source and target domains. Provide or select the name of domain controller of source and target domains (or select Any domain controller) and click Next

Figure 30: Source and target domains selection

Step 5. Click ‘Select computers from domain’ radio button and click Next

Figure 31: Computer selection method

Step 6. Add the desired computer(s) you want to migrate and click Next

Figure 32: Adding computers

Step 7. Click Next

Figure 33: Adding computers

Step 8. Click Browse and choose target OU. Click Next

Figure 34: Choosing target OU

Step 9. Click Next

Figure 35: Choosing target OU

Step 10. Choose Local groups and User rights. Click Next

Figure 36: Computer translation options

Step 11. Choose Replace and click Next. Ignore any warnings

Figure 37: Security translation options

Step 12. Accept the default value and click Next

Figure 38: Computer restart delay

Step 13. Click Next

Figure 39: Computer properties exclusion

Step 14. Click ‘Do not migrate source object if a conflict is detected in the target domain’ radio button and click Next

Figure 40: Computer account conflict management

Step 15. Click Finish

Figure 41: Completing the computer migration wizard

Step 16. Wait for the wizard to complete and look for any errors

Figure 42: Computer migration progress

Step 17. Open Active Directory Users and Computers snap-in and verify the computer account in target OU.

Migrating Large Number of Workstations or Member Servers Using Include File

Follow the steps 1,2,3 and 4 from single computer migration wizard. After step 4, proceed as follow:

– Click ‘Read objects from an include file’ radio button and click Next

Figure 43: Computer selection method

– Click Browse and provide the path of include file on your hard drive. Click Next

Figure 44: Providing include file path

When you are done with above two steps, proceed with step 8 of single computer migration wizard and follow it till the end.

Migrating Objects from Child Domain to Parent Domain Using Command Line

Log in with ADMT migration account on the computer in target or parent domain where ADMT is installed. open PowerShell with elevated privileges and execute one of the following commands. After the migration, open Active Directory Users and Computers snap-in and verify the migrated objects in target OU.

Migrating Limited Users

Execute the following command on PowerShell.

The following table lists the required parameters, explanation and their syntax for migrating user accounts in intraforest.

Table 3: ADMT user command line parameters

Figure 45: Migrating single user using PowerShell

Migrating Large Number of Users Using Include File

Execute the following command on PowerShell.

1

ADMT USER/N“”<user_name>”/IF:YES/SD:<”source_domain”>/TD:<”target_domain”>/TO:<”target_OU”>/MigrateGroups:<YES\NO>/TRP:<YES/NO>/UUR:<YES/NO>

1

ADMT USER/F“<includefile_name>”/IF:YES/SD:<”source_domain”>/TD:<”target_domain”>/TO:<”target_OU”>/MigrateGroups:<YES\NO>/UUR:<YES/NO>/TRP:<YES/NO>

One thought on “Resource Object Assignment Table For Admt

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *