' clonepr.vbt start ' CloneSecurityPrincipal sample VBScript ' ' Clones accounts from one domain to another ' ' Copyright (c) 1999 Microsoft Corporation const SCRIPT_FILENAME = "clonepr.vbs" const SCRIPT_SOURCE_NAME = "clonepr.vbt" const SCRIPT_DATE = "Mar 24 2005" const SCRIPT_TIME = "15:21:37" const ARG_COUNT = 7 ' clonepr.vbi start ' various manifest constants const CLASS_USER = 0 const CLASS_LOCAL_GROUP = 1 const CLASS_GLOBAL_GROUP = 2 const CLASS_OTHER = 3 ' the elements of this array are indexed by the above constants dim classNames(2) classNames(CLASS_USER) = "User" classNames(CLASS_LOCAL_GROUP) = "Group" classNames(CLASS_GLOBAL_GROUP) = "Group" ' from iads.h const ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP = &H4 const ADS_GROUP_TYPE_GLOBAL_GROUP = &H2 const ADS_GROUP_TYPE_UNIVERSAL_GROUP = &H8 const ADS_GROUP_TYPE_SECURITY_ENABLED = &H80000000 const ADS_NAME_INITTYPE_DOMAIN = 1 const ADS_NAME_INITTYPE_SERVER = 2 const ADS_NAME_TYPE_1779 = 1 const ADS_NAME_TYPE_NT4 = 3 const ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12 const ADS_PROPERTY_APPEND = 3 const ADS_PROPERTY_DELETE = 4 const ADS_PROPERTY_UPDATE = 2 ' from lmaccess.h const UF_TEMP_DUPLICATE_ACCOUNT = &H0100 const UF_NORMAL_ACCOUNT = &H0200 ' from andyhar's adsi reskit const ADS_SID_RAW = 0 const ADS_SID_HEXSTRING = 1 const ADS_SID_SDDL = 4 const ADS_SID_WINNT_PATH = 5 const ADS_SID_ACTIVE_DIRECTORY_PATH = 6 const E_ADS_UNKNOWN_OBJECT = &H80005004 const E_ADS_ERROR_DS_NO_SUCH_OBJECT = &H80072030 const E_ADS_ERROR_DS_NAME_NOT_FOUND = &H80072116 ' create the COM object implementing ICloneSecurityPrincipal dim clonepr set clonepr = CreateObject("DSUtils.ClonePrincipal") if Err.Number then DumpErrAndQuit ' create the COM object implementing IADsNameTranslate dim nameTranslate set nameTranslate = CreateObject("NameTranslate") if Err.Number then DumpErrAndQuit ' create the COM object implementing IADsPathname dim adsPathname set adsPathname = CreateObject("Pathname") if Err.Number then DumpErrAndQuit ' create the COM object implementing IADsError dim adsError set adsError = CreateObject("DSUtils.ADsError") if Err.Number then DumpErrAndQuit ' create the COM object implementing IADsSID dim sid set sid = CreateObject("DSUtils.ADsSID") if Err.Number then DumpErrAndQuit ' ' functions and subroutines follow ' sub CloneSecurityPrincipal(byref srcObject, byval srcSam, byval dstDom, byval dstDC, byval dstSam, byval dstDN) on error resume next ' verify that the source object is of a type that we support dim srcObjectClass srcObjectClass = ObjectClass(srcObject) select case srcObjectClass case CLASS_USER if srcObject.UserFlags and UF_TEMP_DUPLICATE_ACCOUNT then Echo "Source object is a temporary local user account, which is not supported." wscript.quit(0) end if case CLASS_LOCAL_GROUP case CLASS_GLOBAL_GROUP ' do nothing case else ' not a supported object class Echo "Source object is of type " & srcObject.Class & ", which is not supported by this tool." wscript.quit(0) end select ' bind to the destination object ' we attempt to locate the destination object by it's sam account name, in ' order to determine if that name is already in use by a security principal ' in the destination domain. dim dstObjectSamPath dstObjectSamPath = "WinNT://" & dstDom & "/" & dstDC & "/" & dstSam dim dstObjectDNPath dstObjectDNPath = "LDAP://" & dstDC & "/" & dstDN dim dstObjectClass dim dstObject Err.Clear set dstObject = GetObject(dstObjectSamPath) dim errnum1 errnum1 = Err.Number select case errnum1 case E_ADS_UNKNOWN_OBJECT ' destination is not found Echo "Destination object " & dstSam & " not found (by SAM name) path used: " & dstObjectSamPath ' bind to the DN of the object, then Err.Clear set dstObject = GetObject(dstObjectDNPath) dim errnum2 errnum2 = Err.Number select case errnum2 case E_ADS_ERROR_DS_NO_SUCH_OBJECT Echo "Destination object " & dstDN & " not found (by DN) path used: " & dstObjectDNPath ' create the dstDN object of the same type as the source Err.Clear set dstObject = CreateDestinationDN(dstSam, dstDN, dstDC, srcObjectClass) case 0 ' dstDN found Echo "Destination DN found" dstObjectClass = ObjectClass(dstObject) if dstObjectClass <> srcObjectClass then Bail "Source and destination objects differ in class type." end if if UCase(dstObject.SamAccountName) <> UCase(dstSam) then ' sam name of the object is not the same as the sam name ' specified on the command line Bail "SAM account name of " & dstDN & " is " & dstObject.SamAccountName & " not " & dstSam end if case else Echo "Error attempting to bind to " & dstObjectDNPath DumpErrAndQuit end select case 0 ' dstSam found. Find the DN of the object it refers to Echo "Destination SAM name found" nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC if Err.Number then DumpErrAndQuit nameTranslate.Set ADS_NAME_TYPE_NT4, dstDom & "\" & dstSam if Err.Number then DumpErrAndQuit dim foundDN foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779) ' aka full DN if Err.Number then DumpErrAndQuit Echo dstSam & " refers to " & foundDN if UCase(dstDN) <> UCase(foundDN) then ' sam name is in use by another object than the one the user ' indicated. Bail "SAM account name " & dstSam & " is in use by object " & foundDN & ", not " & dstDN end if ' at this point, we've verified that the sam name specified by the ' user matches the DN. Now verify that the DN refers to an object ' of the same type as the source set dstObject = GetObject("LDAP://" & dstDC & "/" & foundDN) if Err.Number then DumpErrAndQuit dstObjectClass = ObjectClass(dstObject) if dstObjectClass <> srcObjectClass then Bail "Source and destination objects differ in class type." end if case else Echo "Error attempting to bind to destination object " & dstObjectSamPath DumpErrAndQuit end select ' at this point, dstObject is bound to the object onto which we ' should clone the source object ' copy the source object's properties Echo "Setting properties for target " & dstObject.Class & " " & dstObject.Name select case srcObjectClass case CLASS_USER ' copy the properties of the source user to the destination user clonepr.CopyDownlevelUserProperties srcSam, dstSam, 0 if Err.Number then DumpErrAndQuit Echo "Downlevel properties set." ' fixup the destination user's group memberships FixupUserGroupMemberships srcObject, dstObject, dstDC if Err.Number then DumpErrAndQuit Echo "User's Group memberships restored." ' commit the changes dstObject.SetInfo if Err.Number then DumpErrAndQuit Echo "User changes commited." case CLASS_LOCAL_GROUP ' copy the source group's description if srcObject.Description <> "" then dstObject.Put "Description", srcObject.Description dstObject.SetInfo if Err.Number then DumpErrAndQuit end if Echo "Local group description set." ' copy the source local group's membership CopyLocalGroupMembership srcObject, dstObject if Err.Number then DumpErrAndQuit Echo "Local group membership copied." ' commit the changes dstObject.SetInfo if Err.Number then DumpErrAndQuit Echo "Local group changes commited." case CLASS_GLOBAL_GROUP ' copy the source group's description if srcObject.Description <> "" then dstObject.Put "Description", srcObject.Description dstObject.SetInfo if Err.Number then DumpErrAndQuit end if Echo "Global group description set." ' fixup the destination group's members FixupGlobalGroupMembers srcObject, dstObject, dstDC if Err.Number then DumpErrAndQuit Echo "Global group memberships restored." ' commit the change dstObject.SetInfo if Err.Number then DumpErrAndQuit Echo "Global group changes commited." case else ' why are we here? what is my purpose in life? wscript "illegal code path" wscript.quit(0) end select ' Add the SID of the source principal to the sid history of the destination ' principal. Echo "Adding SID for source " & srcObject.Class & " " & srcObject.Name & " to SID history of target " & dstObject.Class & " " & dstObject.Name clonepr.AddSidHistory srcSam, dstSam, 0 if Err.Number then DumpErrAndQuit Echo "SID history set successfully." ' all done Echo srcObject.Name & " cloned successfully." end sub ' Create a DS security principal object, and return a bound reference to it. ' ' samName - in, sam account name of object-to-be ' ' DN - in, full DN of the object to be created ' ' DC - in, name of domain controller on which the object is to be created ' ' objectClass - in, CLASS_ constant for the type of object to create function CreateDestinationDN(byval samName, byval DN, byval DC, byval objectClass) on error resume next Echo "Creating " & DN ' determine the name of the container to place the new object by removing ' the leaf-most portion of the DN dim p p = InStr(1, DN, ",", 1) dim dstCN dstCN = Mid(DN, 1, p - 1) ' - 1 to omit the comma dim ouDN, ouDNPath ouDN = Mid(DN, p + 1) ' + 1 to skip the comma ouDNPath = "LDAP://" & DC & "/" & ouDN dim container, errnum3 set container = GetObject(ouDNPath) select case Err.Number case E_ADS_ERROR_DS_NO_SUCH_OBJECT Bail "Container " & ouDN & " not found" case 0 ' do nothing case else Echo "Error attempting to bind to " & ouDN DumpErrAndQuit end select dim dstObject set dstObject = container.Create(classNames(objectClass), dstCN) if Err.Number then Echo "Error attempting to create " & DN DumpErrAndQuit end if dstObject.Put "samAccountName", samName if Err.Number then Echo "Error attempting to set samAccountName for " & DN DumpErrAndQuit end if select case objectClass case CLASS_USER ' nothing more to add case CLASS_LOCAL_GROUP ' set group type to local dstObject.Put "groupType", ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP + ADS_GROUP_TYPE_SECURITY_ENABLED if Err.Number then Echo "Error attempting to set local group type for " & DN DumpErrAndQuit end if case CLASS_GLOBAL_GROUP ' set group type to global dstObject.Put "groupType", ADS_GROUP_TYPE_GLOBAL_GROUP + ADS_GROUP_TYPE_SECURITY_ENABLED if Err.Number then Echo "Error attempting to set global group type for " & DN DumpErrAndQuit end if end select dstObject.SetInfo if Err.Number then Echo "Error attempting to commit create of " & DN DumpErrAndQuit end if Echo "Created " & DN set CreateDestinationDN = dstObject end function ' for each group to which the source user object belongs, look for that ' group's sid in the sid histories of objects in the destination forest ' (domain?). If found, add the destination user as a member of the located ' group. Thus, when a user is cloned, the clone becomes a member of all the ' existing cloned groups corresponding to the original groups the ' orignal user belonged to. sub FixupUserGroupMemberships(byref srcObject, byref dstObject, byval dstDC) on error resume next Echo "Fixing group memberships for " & dstObject.Class & " " & dstObject.Name nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC if Err.Number then DumpErrAndQuit dim group dim sidString for each group in srcObject.Groups if (ObjectClass(group) = CLASS_GLOBAL_GROUP) then Echo " Found global group " & group.ADsPath sid.SetAs ADS_SID_WINNT_PATH, group.AdsPath & "," & group.Class if Err.Number then DumpErrAndQuit sidString = sid.GetAs(ADS_SID_SDDL) if Err.Number then DumpErrAndQuit if IsBuiltInSid(sidString) then Echo " " & group.ADsPath & " is a built-in group" ' built-ins are present in every domain with the same sid. So we ' can't search for the corresponding destination object by sid, or ' we may be multiple matches (if there is more than 1 domain in the ' destination forest, and the destination DC also happens to be ' a global catalog). So, here we compose a sid-style LDAP path ' for the built-in destination object. sidString = "" if Err.Number then DumpErrAndQuit dim mypath mypath = "LDAP://" & dstDC & "/" & sidString dim mygroup set mygroup = GetObject(mypath) if Err.Number then DumpErrAndQuit if not IsUserMemberOfGroup(mygroup, dstObject) then Echo " Adding " & dstObject.Name & " to group " & mygroup.Name mygroup.Add dstObject.AdsPath else Echo " " & dstObject.Name & " is already member of " & mygroup.Name end if if Err.Number then DumpErrAndQuit else ' find the DN of the object with that sid as its object sid or in ' its sid history (the sid history is where it will be, if the object ' is a clone). nameTranslate.Set ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, sidString select case Err.Number case E_ADS_ERROR_DS_NAME_NOT_FOUND ' do nothing: skip this member; it hasn't been cloned yet Echo " Skipping " & group.ADsPath & " -- not cloned yet" case 0 ' found! dim foundDN foundDN = "" foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779) ' aka full DN select case Err.Number case E_ADS_ERROR_DS_NAME_NOT_FOUND ' do nothing: skip this member; it hasn't been cloned yet case 0 AddUserToGroup dstObject, foundDN, dstDC case else DumpErrAndQuit end select case else DumpErrAndQuit end select end if else Echo " Skipping group " & group.AdsPath & " -- not global group" end if ' need to clear this so next iteration won't choke. Err.Clear next end sub ' for each member of the source local group, obtain the member's SID and add ' that SID as a member of the destination local group. If that SID does not ' refer to a security principal in the destination domain, then the SAM will ' create a Foreign Principal Object (FPO) to represent that SID. then SAM ' will replace the reference to the SID in the group membership with the DN ' of the FPO. An FPO acts like a proxy for the SID. sub CopyLocalGroupMembership(byref srcObject, byref dstObject) on error resume next Echo "Copying local group membership" ' get the sids in string form of each of the members of the source ' group. collect them in an array dim member dim sidString dim sidStringArray() dim i i = 0 dim dn dn = dstObject.Get("distinguishedName") if Err.Number then DumpErrAndQuit Echo " Getting destination group membership as SIDs" dim dstExistingMemberSIDs dstExistingMemberSIDs = clonepr.GetMembersSIDs(dn) if Err.Number then DumpErrAndQuit dim numExistingMembers numExistingMembers = 0 dim x for each x in dstExistingMemberSIDs numExistingMembers = numExistingMembers + 1 next for each member in srcObject.Members dim sidDeletedAccount if IsDeletedAccount(member.AdsPath, sidDeletedAccount) then Echo " Considering deleted account: " & sidDeletedAccount sid.SetAs ADS_SID_SDDL, sidDeletedAccount else Echo " Considering normal account: " & member.AdsPath sid.SetAs ADS_SID_WINNT_PATH, member.AdsPath & "," & member.Class end if if Err.Number then DumpErrAndQuit sidString = "" if Err.Number then DumpErrAndQuit if (0 = numExistingMembers) Or (not SidStringExists(sidString, dstExistingMemberSIDs)) then Echo " Adding " & sidString redim preserve sidStringArray(i) sidStringArray(i) = sidString i = i + 1 end if next ' use the array to update the destination group in one whack. if i then if 0 = numExistingMembers then dstObject.PutEx ADS_PROPERTY_UPDATE, "member", sidStringArray else dstObject.PutEx ADS_PROPERTY_APPEND, "member", sidStringArray end if if Err.Number then DumpErrAndQuit dstObject.SetInfo if Err.Number then DumpErrAndQuit end if end sub function IsDeletedAccount(byref AdsPath, byref sidDeletedAccount) dim pos0, pos1 pos0 = InStr(1, AdsPath, "://", 1) pos1 = InStr(pos0 + 3, AdsPath, "/", 1) if 0 = pos1 then IsDeletedAccount = True sidDeletedAccount = Mid(AdsPath, pos0 + 3) else IsDeletedAccount = False end if end function function SidStringExists(byref sidString, byref dstExistingMemberSIDs) dim sid sid = UCase(sidString) SidStringExists = False dim x For each x in dstExistingMemberSIDs if UCase(x) = sid then Echo " Skipping existing sid " & x SidStringExists = True exit function end if next end function ' for each member of the source global group, look for that member's sid in ' the sid histories of objects the destination forest (domain?). If found, ' add that located object as a member of the destination group. Thus, ' when a global group is cloned, the existing clones of all users that belong ' to the original group will belong to the cloned group. sub FixupGlobalGroupMembers(byref srcObject, byref dstObject, byval dstDC) on error resume next Echo "Fixing group membership for " & dstObject.Class & " " & dstObject.Name nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC if Err.Number then DumpErrAndQuit dim member dim sidString for each member in srcObject.Members if member.UserFlags and UF_NORMAL_ACCOUNT then ' extract the sid of the account sid.SetAs ADS_SID_WINNT_PATH, member.AdsPath & "," & member.Class if Err.Number then DumpErrAndQuit sidString = sid.GetAs(ADS_SID_SDDL) if Err.Number then DumpErrAndQuit ' find the DN of the member with that sid as its object sid or in ' its sid history (the sid history is where it will be, if the member ' is a clone). nameTranslate.Set ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, sidString select case Err.Number case E_ADS_ERROR_DS_NAME_NOT_FOUND ' do nothing: skip this member; it hasn't been cloned yet case 0 ' found! dim foundDN foundDN = "" foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779) ' aka full DN select case Err.Number case E_ADS_ERROR_DS_NAME_NOT_FOUND ' do nothing: skip this member; it hasn't been cloned yet case 0 ' add the dn to the members property of the dst object dim path path = "LDAP://" & dstDC & "/" & foundDN Dim tempObj set tempObj = GetObject(path) if Err.Number then DumpErrAndQuit if NOT IsUserMemberOfGroup( dstObject, tempObj ) then Echo " adding " & foundDN & " to group " & dstObject.Name dstObject.Add path end if if Err.Number then DumpErrAndQuit case else DumpErrAndQuit end select case else DumpErrAndQuit end select ' need to clear this so the next iteration doesn't choke Err.Clear else ' skip computer, temp and trust accounts Echo " Skipping non-user account " & member.Name end if next end sub ' user - in, reference to user object, bound with LDAP provider. ' ' groupDN - in, full DN of the group to which the user is to be added ' ' dstDC - in, name of destination domain controller sub AddUserToGroup(byref user, byval groupDN, byval dstDC) on error resume next dim path path = "LDAP://" & dstDC & "/" & groupDN dim group set group = GetObject(path) if Err.Number then DumpErrAndQuit if not IsUserMemberOfGroup(group,user) then Echo " Adding " & user.Name & " to group " & group.Name group.Add user.AdsPath else Echo " " & user.Name & " is already member of " & group.Name end if if Err.Number then DumpErrAndQuit end sub function IsUserMemberOfGroup( byref group, byref user ) if group.IsMember(user.AdsPath) then IsUserMemberOfGroup = True exit function end if sid.SetAs ADS_SID_ACTIVE_DIRECTORY_PATH, group.AdsPath if Err.Number then DumpErrAndQuit dim sidString sidString = sid.GetAs(ADS_SID_SDDL) if Err.Number then DumpErrAndQuit if Len(sidString) > 9 then dim lastDash lastDash = InStrRev(sidString, "-", -1, 1) if lastDash then dim ridString ridString = Mid(sidString, lastDash + 1) if StrComp(ridString,user.PrimaryGroupId,1) = 0 then IsUserMemberOfGroup = True exit function end if end if end if IsUserMemberOfGroup = False end function ' based on the class of the object, return one of CLASS_USER, ' CLASS_LOCAL_GROUP, CLASS_GLOBAL_GROUP, CLASS_OTHER function ObjectClass(object) dim cls cls = UCase(object.Class) if cls = "GROUP" then if (object.GroupType and ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP) then ' type is local group ObjectClass = CLASS_LOCAL_GROUP exit function else if ((object.GroupType and ADS_GROUP_TYPE_GLOBAL_GROUP) or (object.GroupType and ADS_GROUP_TYPE_UNIVERSAL_GROUP)) then ' type is global group ObjectClass = CLASS_GLOBAL_GROUP exit function end if end if else if cls = "USER" then ' type is user ObjectClass = CLASS_USER exit function end if end if ' type is not recognized ObjectClass = CLASS_OTHER exit function end function ' returns non-zero if the stringized SID refers to a well-known rid, zero ' otherwise function HasWellKnownRid(byval sidString) ' a SID refers to a well-known account if the first sub-authority (aka ' RID) is < 1000. The first subauthority is the last portion of the ' stringized SID if Len(sidString) > 9 then dim lastDash lastDash = InStrRev(sidString, "-", -1, 1) if lastDash then dim ridString ridString = Mid(sidString, lastDash + 1) if CLng(ridString) < 1000 then HasWellKnownRid = True exit function end if end if end if HasWellKnownRid = False end function ' returns non-zero if the stringized SID refers to a builtin sid, zero ' otherwise function IsBuiltInSid(byval sidString) ' a SID refers to builtin account or group if it has prefix S-1-5-32- if Len(sidString) > 9 then dim prefixString prefixString = Mid(sidString, 1, 9) if StrComp( prefixString, "S-1-5-32-", 1 ) = 0 then IsBuiltInSid = true exit function end if end if IsBuiltInSid = False end function ' searches for and returns the value of a command line argument of the form ' /argName:value from the supplied array. erases the entry in the array so ' that only untouched entries remain. function GetArgValue(argName, args()) dim a dim v dim argNameLength dim x dim argCount dim fullArgName fullArgName = "/" & argName & ":" argCount = Ubound(args) ' Get the length of the argname we are looking for argNameLength = Len(fullArgName) GetArgValue = "" ' default to nothing for x = 0 To argCount if Len(args(x)) >= argNameLength then a = Mid(args(x), 1, argNameLength) if UCase(a) = UCase(fullArgName) then ' erase it so we can look for unknown args later v = args(x) args(x) = "" if Len(v) > argNameLength then GetArgValue = Mid(v, argNameLength + 1) exit function else GetArgValue = "" exit function end if end if end if next end function ' walks thru the array searching for any non-empty element. if at least one ' is found, then return non-zero. Otherwise return 0. function CheckForBadArgs(byref args()) dim i for i = 0 to UBound(args) if Len(args(i)) > 0 then CheckForBadArgs = 1 exit function end if next CheckForBadArgs = 0 end function sub DumpErrAndQuit dim errnum errnum = Err.Number Echo "Error 0x" & CStr(Hex(errnum)) & " occurred." if len(Err.Description) then Echo "Error Description: " & Err.Description end if if len(Err.Source) then Echo "Error Source : " & Err.Source end if Echo "ADsError Description: " Echo adsError.GetErrorMsg(errnum) wscript.quit(0) end sub sub Bail(byref message) Echo "Error: " & message wscript.quit(0) end sub sub Echo(byref message) wscript.echo message end sub ' clonepr.vbi end Main wscript.quit(0) sub Main if wscript.arguments.count <> ARG_COUNT then PrintUsageAndQuit end if ' copy the command-line arguments for parsing dim args() Redim args(0) args(0) = "" dim i for i = 0 to wscript.arguments.count - 1 Redim Preserve args(i) args(i) = wscript.arguments.item(i) next ' command line parameters dim srcDC ' source domain controller dim srcDom ' source domain dim srcSam ' source principal SAM name dim dstDC ' destination controller dim dstDom ' destination domain dim dstSam ' destination principal SAM name dim dstCN ' CN=dstSam dim dstCNnew ' CN=dstSam, escaped dim dstDNTmp ' destination principal Full Distinguished Name dim dstDN ' destination principal Full Distinguished Name, escaped ' parse the saved command-line arguments, extracting the values srcDC = GetArgValue("srcdc", args) srcDom = GetArgValue("srcdom", args) srcSam = GetArgValue("srcsam", args) dstDC = GetArgValue("dstdc", args) dstDom = GetArgValue("dstdom", args) dstSam = GetArgValue("dstsam", args) dstDNTmp= GetArgValue("dstdn", args) dstCN = "CN=" & dstSam dstCNnew= adsPathname.GetEscapedElement(0, dstCN) If (UCase(dstCN) <> UCase(dstCNnew)) And (UCase(dstCN) = UCase(Left(dstDNTmp, Len(dstCN)))) Then dstDN = dstCNnew & Mid(dstDNTmp, Len(dstCN) + 1) Else dstDN = dstDNTmp End If ' ensure the user did not pass any unrecognized command-line arguments if CheckForBadArgs(args) then Echo "Unknown command-line arguments specified" PrintUsageAndQuit end if ' establish authenticate connections to the source and destination domain ' controllers on error resume next clonepr.Connect srcDC, srcDom, dstDC, dstDom if Err.Number then DumpErrAndQuit Echo "Connected to source and destination domain controllers" ' bind to the source object dim srcPath srcPath = "WinNT://" & srcDom & "/" & srcDC & "/" & srcSam dim srcObject set srcObject = GetObject(srcPath) select case Err.Number case E_ADS_UNKNOWN_OBJECT Bail "Source object " & srcSam & " not found. Path used: " & srcPath case 0 ' do nothing case else DumpErrAndQuit end select Echo "Bound to source " & srcObject.Class & " " & srcObject.Name if ShouldCloneObject(srcObject) then CloneSecurityPrincipal srcObject, srcSam, dstDom, dstDC, dstSam, dstDN end if end sub function ShouldCloneObject(byref srcObject) on error resume next sid.SetAs ADS_SID_WINNT_PATH, srcObject.AdsPath & "," & srcObject.Class if Err.Number then DumpErrAndQuit dim sidString sidString = sid.GetAs(ADS_SID_SDDL) if Err.Number then DumpErrAndQuit if IsBuiltInSid( sidString ) then Echo srcObject.Name & " is a builtin Account." Echo "BuiltIn Users and Groups cannot be cloned" ShouldCloneObject = False exit function end if ShouldCloneObject = True end function sub PrintUsageAndQuit Echo "Usage: cscript " & SCRIPT_FILENAME & " /srcdc: /srcdom:" Echo "/srcsam: /dstdc: /dstdom: /dstsam:" Echo "/dstdn" Echo "" Echo "Parameters:" Echo " /srcdc - source domain controller NetBIOS computer name (without leading \\)" Echo "" Echo " /srcdom - source domain NetBIOS name" Echo "" Echo " /srcsam - source principal SAM name" Echo "" Echo " /dstdc - destination domain controller NetBIOS computer name (without " Echo " leading \\)" Echo " This script must be run on the machine indicated here." Echo "" Echo " /dstdom - destination domain DNS name" Echo "" Echo " /dstsam - destination principal SAM name" Echo "" Echo " /dstdn - destination principal Full Distinguished Name" Echo "" Echo "Notes:" Echo "" Echo "If the destination principal does not exist, it will be created." Echo "In that case, the container naming context of the destination Full" Echo "Distinguished Name (i.e. the parent container) must exist." Echo "" Echo "Currently logged-on user must be a member of the Administrators" Echo "group of both the source and destination domains." Echo "" Echo SCRIPT_DATE & " " & SCRIPT_TIME wscript.quit(0) end sub ' clonepr.vbt end