Set Associated external account property through script

Hi
Is there a way to set the "associated external account" property and "full mailbox access" property on the Exchange advanced tab of multiple users, to their respective accounts from a different forest(trust enabled) through script/

Regards
Harsha
ASAdminAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chris DentPowerShell DeveloperCommented:

But of course :)

I did one the other day, let me dig it out again.

Chris
0
Chris DentPowerShell DeveloperCommented:
Here we go, this is all in VbScript so it'll need saving as .vbs.

It basically takes a CSV File containing the User names you (one in each domain) then reads the first as the account with the mailbox, and the second as the one you want to set as Associated External Account.

You'll need to fix a few of the Constants at the top for it to work (as it says) otherwise I recommend testing it on a small number of users first (one would be good).

Chris
Option Explicit
 
' The script can find everything else, but it must be told which domains it's to work against.
' Fully qualified domain names for both (domain, not server).
 
Const EXCHANGE_DOMAIN = "exchangedomain.local"
Const EXTERNAL_DOMAIN = "externaldomain.local"
 
' Set the Filename to read usernames from. Expects Comma Delimited List <ExchangeUsername>,<ExternalUsername>
 
Const FILE_NAME = "Filename.csv"
 
'
' Subroutines
'
 
Sub ReadFile
	Dim objFileSystem, objFile
	Dim strLine, strExchangeUsername, strExternalUsername
	Dim arrLine
 
	' Connect to the File
 
	Set objFileSystem = CreateObject("Scripting.FileSystemObject")
	Set objFile = objFileSystem.OpenTextFile(FILE_NAME, 1, False, 0)
 
	' Read all lines in the File, adding each unique username to a Dictionary
 
	Do While Not objFile.AtEndOfStream
		strLine = objFile.ReadLine
		arrLine = Split(strLine, ",")
 
		strExchangeUsername = LCase(arrLine(0))
		strExternalUsername = arrLine(1)
 
		If Not objUsers.Exists(strExchangeUserName) Then
			objUsers.Add strExchangeUsername, Array(strExternalUsername, "")
		End If
	Loop
 
	Set objFile = Nothing
	Set objFileSystem = Nothing
End Sub
 
Sub GetADData
	' Update each of the user accounts in objUsers with a DistinguishedName
 
	Const ADS_SCOPE_SUBTREE = 2
 
	Dim objConnection, objCommand, objRecordSet, objRootDSE
	Dim strUserName, strDN
	Dim arrData
 
	Set objConnection = CreateObject("ADODB.Connection")
	objConnection.Provider = "ADsDSOObject"
	objConnection.Open "Active Directory Provider"
 
	Set objCommand = CreateObject("ADODB.Command")
	objCommand.ActiveConnection = objConnection
 
	' This bit might be slow if the DCs are widely spread over slow connections.
	' May need revision to cope with that.
 
	Set objRootDSE = GetObject("LDAP://" & EXCHANGE_DOMAIN & "/RootDSE")
	objCommand.CommandText = "SELECT distinguishedName, sAMAccountName " &_
		"FROM 'LDAP://" & EXCHANGE_DOMAIN & "/" & objRootDSE.Get("defaultNamingContext") &_
		"' WHERE objectClass='user' AND objectCategory='person'"
	Set objRootDSE = Nothing
 
	objCommand.Properties("Page Size") = 1000
	objCommand.Properties("Timeout") = 600
	objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
	objCommand.Properties("Cache Results") = False
 
	Set objRecordSet = objCommand.Execute
 
	' Update all entries in the Dictionary with a Distinguished Name
 
	While Not objRecordSet.EOF
		strUsername = objRecordSet.Fields("sAMAccountName")
		strDN = objRecordSet.Fields("distinguishedName")
		If objUsers.Exists(strUsername) Then
			arrData = objUsers(strUsername)
			arrData(1) = strDN
 
			objUsers(strUsername) = arrData
		End If
		objRecordSet.MoveNext
	Wend
	objConnection.Close
 
	Set objRecordSet = Nothing
	Set objCommand = Nothing
	Set objConnection = Nothing
End Sub
 
Sub DisableAccounts
	' Check each account in the Exchange Domain is Disabled
 
	Const ADS_UF_ACCOUNTDISABLE = &H2
 
	Dim strUsername, strDN
	Dim objUser
	Dim intUAC
 
	For Each strUsername in objUsers
		strDN = objUsers(strUsername)(1)
 
		Set objUser = GetObject("LDAP://" & strDN)
		intUAC = objUser.Get("userAccountControl")
 
		' Look for the Account Disable Flag in userAccountControl
 
		If intUAC And ADS_UF_ACCOUNTDISABLE Then
			' Account is already Disabled
		Else
			' Disable the Account
			intUAC = intUAC XOr ADS_UF_ACCOUNTDISABLE
			objUser.Put "userAccountControl", intUAC
			objUser.SetInfo
		End If
		Set objUser = Nothing
	Next
End Sub
 
Function GetNetBIOSDomainName(strDomain)
	' Modified version of sample code from "Active Directory Cookbook" by Robbie Allen
 
	Dim objRootDSE, objConnection, objRecordSet
	Dim strADSPath, strFilter, strAttrs, strScope
 
	Set objRootDSE = GetObject("LDAP://" & strDomain & "/RootDSE")
 
	strADsPath =  "<LDAP://" & strDomain & "/CN=Partitions," & _
		objRootDSE.Get("configurationNamingContext") & ">;"
 
	strFilter = "(&(objectcategory=Crossref)" & _
		"(dnsRoot=" & strDomain & ")(netBIOSName=*));"
	strAttrs = "netbiosname;"
	strScope = "Onelevel"
 
	Set objConnection = CreateObject("ADODB.Connection")
	objConnection.Provider = "ADsDSOObject"
	objConnection.Open "Active Directory Provider"
 
	Set objRecordSet = objConnection.Execute(strADsPath & strFilter & strAttrs & strScope)
	objRecordSet.MoveFirst
 
	GetNetBIOSDomainName = objRecordSet.Fields("netbiosName")
 
	Set objRecordSet = Nothing
	Set objConnection = Nothing
End Function
 
Sub SetAEA
	' Add an Associated External Account Access Control Entry to the Access Control List
	' for each of the users we matched up.
 
	Const ADS_ACETYPE_ACCESS_ALLOWED = &H00000
	Const ADS_ACEFLAG_INHERIT_ACE = &H00002
 
	Const ADS_RIGHT_FULL_MB_ACCESS = &H00001
	Const ADS_RIGHT_ASSOCIATED_EXTERNAL_ACCOUNT = &H00004
 
	Dim objUser, objMailboxSD, objDACL, objACE
	Dim strExchangeUsername, strDN, strExternalUsername, strNetBIOSDomain
	Dim booFoundAEA
 
	strNetBIOSDomain = GetNetBIOSDomainName(EXTERNAL_DOMAIN)
 
	For Each strExchangeUsername in objUsers
		strDN = objUsers(strExchangeUsername)(1)
		strExternalUsername = objUsers(strExchangeUsername)(0)
 
		' Don't run this for Users we didn't manage to get the DistinguishedName for
 
		If strDN <> "" Then
			Set objUser = GetObject("LDAP://" & strDN)
	
			' Grab the Mailbox Access Control List from the User Account
 
			Set objMailboxSD = objUser.MailboxRights
			Set objDACL = objMailboxSD.DiscretionaryAcl
	
			booFoundAEA = False
 
			' Check the existing Access Control List. If we find as Associated External Account
			' matching the one we'd add we'll just skip it.
	
			For Each objACE in objDACL
				If objACE.AccessMask And ADS_RIGHT_ASSOCIATED_EXTERNAL_ACCOUNT Then
					If LCase(objACE.Trustee) <> _ 
							LCase(strNetBIOSDomain & "\" & strExternalUsername) Then
	
						' If it doesn't match the one we want we'll remove the current ACE
	
						objDACL.RemoveAce objACE
					Else
						booFoundAEA = True
					End If
				End If
			Next
			Set objACE = Nothing
 
			' Create a new Access Control Entry and add it to the DACL (if there's not one already)
 
			If booFoundAEA = False Then
				Set objACE = CreateObject("AccessControlEntry")
	
				objACE.Trustee = strNetBIOSDomain & "\" & strExternalUsername
				objACE.AccessMask = ADS_RIGHT_FULL_MB_ACCESS + _
					ADS_RIGHT_ASSOCIATED_EXTERNAL_ACCOUNT
				objACE.AceFlags = ADS_ACEFLAG_INHERIT_ACE
				objACE.AceType = ADS_ACETYPE_ACCESS_ALLOWED
 
				' If the SID exists in both Domains this will cause an error
				' Normally occurs if ADMT has been used to shift a user account 
				' while maintaining the SID (sidHistory).
	
				objDACL.AddAce(objACE)
				objMailboxSD.DiscretionaryAcl = objDACL
				objUser.MailboxRights = Array(objMailboxSD)
				objUser.SetInfo
			End If
	
			Set objACE = Nothing
			Set objMailboxSD = Nothing
			Set objDACL = Nothing
 
			Set objUser = Nothing
		End If
	Next
End Sub
 
'
' Main Code
'
 
Dim objUsers
 
' Create a Dictionary ("key, value" list)to hold the Users in
 
Set objUsers = CreateObject("Scripting.Dictionary")
 
ReadFile
GetADData
DisableAccounts
SetAEA
 
' Cleanup
 
Set objUsers = Nothing

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
ASAdminAuthor Commented:
Fantastic,
I will test it out in my test lab today after making the necessary modifications.
does the csv file contain just two fields(ofcourse thats all it needs anyways) .
And the next thing is will the script also set the full mailbox access property as well?

Thank you so much.
0
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

Chris DentPowerShell DeveloperCommented:

Hey,

Yep, just those two fields.

And yes, it sets that. This is the bit that sets the Mask:

objACE.AccessMask = ADS_RIGHT_FULL_MB_ACCESS + _
      ADS_RIGHT_ASSOCIATED_EXTERNAL_ACCOUNT

And since Associated External isn't much good without mailbox access we have both :)

Chris
0
ASAdminAuthor Commented:
Fantastic Chris,
I will test that today in my lab and then assign you points.

Than you
0
Chris DentPowerShell DeveloperCommented:

No problem, yell if you have any problems with it. Did test it as much as I could here, the only problem I ran into was where the SID for the Associated External Account exists in both domains (if ADMT has been run and the SID is present in a SID history).

Chris
0
ASAdminAuthor Commented:
One more quick question, Do we have to disable the account on which we are setting these properties?
I am asking this question from two perspectives

1. If we disable the account, does it recieve emails?
2. What if we need to login as that account in the old (Exchange domain)?

Thankyou
0
Chris DentPowerShell DeveloperCommented:

No, you don't have to disable the source if you don't want to, sorry I forgot to note that was included.

Feel free to remove the call to the Subroutine DisableAccounts under Main Code (or remove that and the Subroutine that does it). It's intentionally modular so it'll happily survive without that step.

1. Yes provided that an Associated External Account is set
2. It won't let you :)

Chris
0
ASAdminAuthor Commented:
Perfect, I'd rather remove that module.

Thank you.
0
ASAdminAuthor Commented:
one more quick question, on the csv file, is there a header row or just the values through out( I am thinking it is the sAMAccountName that we are going to put in that field, correct me if I am wrong).

thank you
0
ASAdminAuthor Commented:
Hi Chris,
I am getting the fallowing error trying to run that script.

Line:182
Char: 4
Object doesn't support this property or method, objuser.Mailboxrights
Code: 800A01B6

Regards
0
ASAdminAuthor Commented:
Never mind, I ran it on the exchange server in the source and it worked smooth, no problems at all.,
0
ASAdminAuthor Commented:
Thank you Chris for helping me with this.
0
Chris DentPowerShell DeveloperCommented:

Excellent :) Should have mentioned that it must run run from a system with the Exchange System Tools installed.

Chris
0
Chris DentPowerShell DeveloperCommented:

Missed a bit:

> is there a header row or just the values through out

Just the values, I didn't write anything in to account for a header row.

Chris
0
moe9107Commented:
Hi,
I have tried this script but I get the following error.
"The specified directory service attribute or value does not exist" Line 221

I have migrated the user accounts from one forest to another while leaving Exchange in the source forest. The users are migrated with SID history as some resources are remaining in the source forest for the time being. I was wondering if the error is due to the SID history and whether there is a way to get the script modified to allow it to work in this situation.

Thanks in advance
0
Chris DentPowerShell DeveloperCommented:

Will it let you manually assign the permission using the GUI? If it does, then yes, there's a chance we can fix it. If it doesn't we're a bit stuck.

Chris
0
MikeEddCommented:
I too need to be able to preserve SID history during a ADMT migration. and I need to associate to a  external account(the old email account on the old domain) I can do this via the GUI, does this script work with SID History?

MikeEdd
0
Chris DentPowerShell DeveloperCommented:

As above, if you can do it in the GUI the script can deal with it. If you cannot then it won't be able to help.

Chris
0
markpalinuxCommented:
Thanks  - this helped me a ton.

It set the both things I needed.
Full mailbox Access
 Associated external account

Thank you,
Mark Leddy
0
Jacqueline-RathboneCommented:
Hi there,

This is the best thing i have discovered since starting on a MAJOR domain migration. Thanks :)

I am also running into the SID history issue (Yes ADMT was used to migrate users)

To answer the question about if its possible in the GUI... yes, i can do all the script tasks via GUI.

(I am an absolute scripting idiot - so this is just a dangerous errant though)
Since SID history is the issue here, are there perhaps some of the mailbox permissions that dont need to be set? - hence avoiding the error?

I still have almost 10k users to migrate in the next 2 weeks - so i would LOVE to get this working.

Regards
Jacqueline

PS. i did add one thing to the script.
I added a section to update the mailNickName in AD (with the 'strExternalUsername') - its probably only in my environment that this is required.
0
Chris DentPowerShell DeveloperCommented:

> Since SID history is the issue here, are there perhaps some of the mailbox permissions that dont need to be set?
> - hence avoiding the error?

Not really I'm afraid. Existing permissions will be bound to SELF rather than an explicit entry for the SID.

Chris
0
ASAdminAuthor Commented:
Hmm!!
When I created this question, I was using Quest migration tools for that perticular migraion. Later our company aquired another company(smaller one) and needed to merge that domain into ours and I used ADMT since our company didn't want spend the ransom on Quest, and the script still worked fine.
0
Jacqueline-RathboneCommented:
@ ASAdmin

You probably used ADMT, but didnt carry the SID history with... i WISH our company went with that idea... instead they went the FULL microsoft idea... with all its issues.

let me go play with this some more :)
0
Chris DentPowerShell DeveloperCommented:

It was written to work alongside a migration based on ADMT, it should be fine :)

Chris
0
Jacqueline-RathboneCommented:
I get the following error :
>>User-Mig.vbs(109, 3) (null): 0x80005000
line 109 in my scriptfile is: Set objUser = GetObject("LDAP://" & strDN)

But i only get this on accounts that were migrated with ADMT. my test accounts dont get this - they work fine.
0
Jacqueline-RathboneCommented:
Ok... fixed it.
Replaced the "diableuser" sub with different code... .works fine :)

(dont ask my why of all things that didnt work.... my coding skills are just not good enough)

Thanks a million... this makes my target of 10k users in the next 2 weeks possible :)
0
Chris DentPowerShell DeveloperCommented:

Good luck Jacqueline :)

Chris
0
Jacqueline-RathboneCommented:
I fixed it :)

Line 33: strExchangeUsername = LCase(arrLine(0))

Took out the  LCase statement... most my usernames have Caps in....

Now the script works (in its original form) accross the board.

i suppose i could have added another  LCase statement into line 82 If objUsers.Exists(strUsername) Then

the main thing, is that it now works on my domain.... (i didnt realise that my test accounts were all lowercase) until i stumbled onto the issue.
0
Chris DentPowerShell DeveloperCommented:

You can make objUsers case insensitive:


Set objUsers = CreateObject("Scripting.Dictionary")
objUsers.CompareMove = vbTextCompare


Otherwise LCase as you thought.

Chris
0
Chris DentPowerShell DeveloperCommented:

er should have been:


objUsers.CompareMode = vbTextCompare


Chris
0
Jacqueline-RathboneCommented:
ERm... now i am going to sound like an idiot....

where do i add that in???? (i think i fried my brain already today... no more thinking power left)
0
Chris DentPowerShell DeveloperCommented:

Immediately after this line (243 in the original):

Set objUsers = CreateObject("Scripting.Dictionary")

That creates the dictionary object, by default it does binary comparison, effectively case sensitive comparison. By changing CompareMode you can make it ignore case, so you end up with this in place of the original:

Set objUsers = CreateObject("Scripting.Dictionary")
objUsers.CompareMode = VbTextCompare

Chris
0
Jacqueline-RathboneCommented:
Thanks a million :)
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Exchange

From novice to tech pro — start learning today.