home *** CD-ROM | disk | FTP | other *** search
/ ftp.ac-grenoble.fr / 2015.02.ftp.ac-grenoble.fr.tar / ftp.ac-grenoble.fr / assistance.logicielle / XP_ServicePack-3.iso / support / tools / support.cab / cloneggu.vbs < prev    next >
Text File  |  2001-08-17  |  33KB  |  1,128 lines

  1.  
  2. ' cloneggu.vbt start
  3.  
  4. ' CloneSecurityPrincipal VBScript
  5. '
  6. ' Clone all Global Groups and Users in a domain 
  7. '
  8. ' Copyright (C) 1999 Microsoft Corporation.
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21. ' clonepr.vbi start
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31. ' various manifest constants
  32. const CLASS_USER         = 0
  33. const CLASS_LOCAL_GROUP  = 1
  34. const CLASS_GLOBAL_GROUP = 2
  35. const CLASS_OTHER        = 3
  36.  
  37. ' the elements of this array are indexed by the above constants
  38. dim classNames(2)
  39. classNames(CLASS_USER)         = "User" 
  40. classNames(CLASS_LOCAL_GROUP)  = "Group"
  41. classNames(CLASS_GLOBAL_GROUP) = "Group"
  42.  
  43. ' from iads.h
  44. const ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP     = &H4       
  45. const ADS_GROUP_TYPE_GLOBAL_GROUP           = &H2
  46. const ADS_GROUP_TYPE_UNIVERSAL_GROUP        = &H8
  47. const ADS_GROUP_TYPE_SECURITY_ENABLED       = &H80000000
  48. const ADS_NAME_INITTYPE_DOMAIN              = 1         
  49. const ADS_NAME_INITTYPE_SERVER              = 2         
  50. const ADS_NAME_TYPE_1779                    = 1         
  51. const ADS_NAME_TYPE_NT4                     = 3         
  52. const ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12        
  53. const ADS_PROPERTY_APPEND                   = 3         
  54. const ADS_PROPERTY_DELETE                   = 4         
  55. const ADS_PROPERTY_UPDATE                   = 2         
  56.  
  57. ' from lmaccess.h
  58. const UF_TEMP_DUPLICATE_ACCOUNT = &H0100
  59. const UF_NORMAL_ACCOUNT         = &H0200
  60.  
  61. ' from andyhar's adsi reskit
  62. const ADS_SID_RAW                   = 0
  63. const ADS_SID_HEXSTRING             = 1
  64. const ADS_SID_SDDL                  = 4
  65. const ADS_SID_WINNT_PATH            = 5
  66. const ADS_SID_ACTIVE_DIRECTORY_PATH = 6
  67.  
  68. const E_ADS_UNKNOWN_OBJECT          = &H80005004
  69. const E_ADS_ERROR_DS_NO_SUCH_OBJECT = &H80072030
  70. const E_ADS_ERROR_DS_NAME_NOT_FOUND = &H80072116
  71.  
  72.  
  73.  
  74. ' create the COM object implementing ICloneSecurityPrincipal
  75. dim clonepr
  76. set clonepr = CreateObject("DSUtils.ClonePrincipal")
  77. if Err.Number then DumpErrAndQuit
  78.  
  79. ' create the COM object implementing IADsNameTranslate
  80. dim nameTranslate
  81. set nameTranslate = CreateObject("NameTranslate")
  82. if Err.Number then DumpErrAndQuit
  83.  
  84. ' create the COM object implementing IADsPathname
  85. dim adsPathname
  86. set adsPathname = CreateObject("Pathname")
  87. if Err.Number then DumpErrAndQuit
  88.  
  89. ' create the COM object implementing IADsError
  90. dim adsError
  91. set adsError = CreateObject("DSUtils.ADsError")
  92. if Err.Number then DumpErrAndQuit
  93.  
  94. ' create the COM object implementing IADsSID
  95. dim sid
  96. set sid = CreateObject("DSUtils.ADsSID")
  97. if Err.Number then DumpErrAndQuit
  98.  
  99.  
  100.  
  101. '
  102. ' functions and subroutines follow
  103. '
  104.  
  105.  
  106. sub CloneSecurityPrincipal(byref srcObject, byval srcSam, byval dstDom, byval dstDC, byval dstSam, byval dstDN)
  107.    on error resume next
  108.  
  109.    ' verify that the source object is of a type that we support
  110.    dim srcObjectClass
  111.    srcObjectClass = ObjectClass(srcObject)
  112.  
  113.     select case srcObjectClass
  114.         case CLASS_USER
  115.             if srcObject.UserFlags and UF_TEMP_DUPLICATE_ACCOUNT then
  116.                 Echo "Source object is a temporary local user account, which is not supported."
  117.              wscript.quit(0)            
  118.             end if 
  119.       case CLASS_LOCAL_GROUP
  120.       case CLASS_GLOBAL_GROUP
  121.          ' do nothing
  122.       case else
  123.          ' not a supported object class
  124.          Echo "Source object is of type " & srcObject.Class & ", which is not supported by this tool."
  125.          wscript.quit(0)
  126.    end select
  127.  
  128.    ' bind to the destination object
  129.  
  130.    ' we attempt to locate the destination object by it's sam account name, in
  131.    ' order to determine if that name is already in use by a security principal
  132.    ' in the destination domain.
  133.  
  134.    dim dstObjectSamPath
  135.    dstObjectSamPath = "WinNT://" & dstDom & "/" & dstDC & "/" & dstSam
  136.  
  137.    dim dstObjectDNPath
  138.    dstObjectDNPath = "LDAP://" & dstDC & "/" & dstDN
  139.  
  140.    dim dstObjectClass
  141.    dim dstObject
  142.  
  143.    Err.Clear
  144.    set dstObject = GetObject(dstObjectSamPath)
  145.    dim errnum1
  146.    errnum1 = Err.Number
  147.    select case errnum1
  148.       case E_ADS_UNKNOWN_OBJECT
  149.          ' destination is not found
  150.  
  151.          Echo "Destination object " & dstSam & " not found (by SAM name) path used: " & dstObjectSamPath
  152.  
  153.          ' bind to the DN of the object, then
  154.          Err.Clear
  155.          set dstObject = GetObject(dstObjectDNPath)
  156.          dim errnum2
  157.          errnum2 = Err.Number
  158.          select case errnum2
  159.             case E_ADS_ERROR_DS_NO_SUCH_OBJECT
  160.                Echo "Destination object " & dstDN & " not found (by DN) path used: " & dstObjectDNPath
  161.  
  162.                ' create the dstDN object of the same type as the source
  163.                Err.Clear
  164.                set dstObject = CreateDestinationDN(dstSam, dstDN, dstDC, srcObjectClass)
  165.  
  166.             case 0
  167.                ' dstDN found
  168.  
  169.                Echo "Destination DN found"
  170.  
  171.                dstObjectClass = ObjectClass(dstObject)
  172.  
  173.                if dstObjectClass <> srcObjectClass then
  174.                   Bail "Source and destination objects differ in class type."
  175.                end if
  176.  
  177.                if UCase(dstObject.SamAccountName) <> UCase(dstSam) then
  178.                   ' sam name of the object is not the same as the sam name
  179.                   ' specified on the command line
  180.                   Bail "SAM account name of " & dstDN & " is " & dstObject.SamAccountName & " not " & dstSam
  181.                end if
  182.  
  183.             case else
  184.                Echo "Error attempting to bind to " & dstObjectDNPath
  185.                DumpErrAndQuit
  186.  
  187.          end select
  188.  
  189.       case 0
  190.          ' dstSam found.  Find the DN of the object it refers to
  191.  
  192.          Echo "Destination SAM name found"
  193.  
  194.          nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC
  195.          if Err.Number then DumpErrAndQuit
  196.  
  197.          nameTranslate.Set ADS_NAME_TYPE_NT4, dstDom & "\" & dstSam
  198.          if Err.Number then DumpErrAndQuit
  199.  
  200.          dim foundDN
  201.          foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779)  ' aka full DN
  202.          if Err.Number then DumpErrAndQuit
  203.  
  204.          Echo dstSam & " refers to " & foundDN
  205.  
  206.          if UCase(dstDN) <> UCase(foundDN) then
  207.             ' sam name is in use by another object than the one the user
  208.             ' indicated.
  209.             Bail "SAM account name " & dstSam & " is in use by object " & foundDN & ", not " & dstDN
  210.          end if
  211.  
  212.          ' at this point, we've verified that the sam name specified by the
  213.          ' user matches the DN.  Now verify that the DN refers to an object
  214.          ' of the same type as the source  
  215.  
  216.          set dstObject = GetObject("LDAP://" & dstDC & "/" & foundDN)
  217.          if Err.Number then DumpErrAndQuit
  218.  
  219.          dstObjectClass = ObjectClass(dstObject)
  220.          if dstObjectClass <> srcObjectClass then
  221.             Bail "Source and destination objects differ in class type."
  222.          end if
  223.  
  224.       case else
  225.          Echo "Error attempting to bind to destination object " & dstObjectSamPath
  226.          DumpErrAndQuit
  227.    end select
  228.  
  229.    ' at this point, dstObject is bound to the object onto which we
  230.    ' should clone the source object 
  231.  
  232.    ' copy the source object's properties
  233.    Echo "Setting properties for target " & dstObject.Class & " " & dstObject.Name
  234.    select case srcObjectClass
  235.       case CLASS_USER
  236.  
  237.          ' copy the properties of the source user to the destination user
  238.          clonepr.CopyDownlevelUserProperties srcSam, dstSam, 0
  239.          if Err.Number then DumpErrAndQuit
  240.  
  241.          Echo "Downlevel properties set."
  242.  
  243.          ' fixup the destination user's group memberships
  244.  
  245.          FixupUserGroupMemberships srcObject, dstObject, dstDC
  246.          if Err.Number then DumpErrAndQuit
  247.  
  248.          Echo "User's Group memberships restored."
  249.  
  250.          ' commit the changes
  251.          dstObject.SetInfo
  252.          if Err.Number then DumpErrAndQuit
  253.  
  254.          Echo "User changes commited."
  255.  
  256.       case CLASS_LOCAL_GROUP
  257.          ' copy the source group's description
  258.          if srcObject.Description <> "" then 
  259.             dstObject.Put "Description", srcObject.Description
  260.             dstObject.SetInfo
  261.             if Err.Number then DumpErrAndQuit
  262.          end if
  263.  
  264.          Echo "Local group description set."
  265.  
  266.          ' copy the source local group's membership
  267.          CopyLocalGroupMembership srcObject, dstObject
  268.          if Err.Number then DumpErrAndQuit
  269.  
  270.          Echo "Local group membership copied."
  271.  
  272.          ' commit the changes
  273.          dstObject.SetInfo
  274.          if Err.Number then DumpErrAndQuit
  275.  
  276.          Echo "Local group changes commited."
  277.  
  278.       case CLASS_GLOBAL_GROUP
  279.          ' copy the source group's description
  280.          if srcObject.Description <> "" then 
  281.             dstObject.Put "Description", srcObject.Description
  282.             dstObject.SetInfo
  283.             if Err.Number then DumpErrAndQuit
  284.          end if
  285.  
  286.          Echo "Global group description set."
  287.  
  288.          ' fixup the destination group's members
  289.          FixupGlobalGroupMembers srcObject, dstObject, dstDC
  290.          if Err.Number then DumpErrAndQuit
  291.  
  292.          Echo "Global group memberships restored."
  293.  
  294.          ' commit the change
  295.          dstObject.SetInfo
  296.          if Err.Number then DumpErrAndQuit
  297.  
  298.          Echo "Global group changes commited."
  299.  
  300.       case else
  301.          ' why are we here?  what is my purpose in life?
  302.          wscript "illegal code path"
  303.          wscript.quit(0)
  304.  
  305.    end select
  306.  
  307.    ' Add the SID of the source principal to the sid history of the destination
  308.    ' principal.
  309.    Echo "Adding SID for source " & srcObject.Class & " " & srcObject.Name & " to SID history of target " & dstObject.Class & " " & dstObject.Name
  310.    clonepr.AddSidHistory srcSam, dstSam, 0
  311.    if Err.Number then DumpErrAndQuit
  312.  
  313.    Echo "SID history set successfully."
  314.  
  315.    ' all done
  316.    Echo srcObject.Name & " cloned successfully."
  317. end sub
  318.  
  319.  
  320.  
  321. ' Create a DS security principal object, and return a bound reference to it.
  322. ' samName - in, sam account name of object-to-be
  323. '
  324. ' DN - in, full DN of the object to be created
  325. '
  326. ' DC - in, name of domain controller on which the object is to be created
  327. '
  328. ' objectClass - in, CLASS_ constant for the type of object to create
  329.  
  330. function CreateDestinationDN(byval samName, byval DN, byval DC, byval objectClass)
  331.    on error resume next
  332.    Echo "Creating " & DN
  333.  
  334.    ' determine the name of the container to place the new object by removing
  335.    ' the leaf-most portion of the DN
  336.    dim p
  337.    p = InStr(1, DN, ",", 1)
  338.  
  339.    dim dstCN
  340.    dstCN = Mid(DN, 1, p - 1)  ' - 1 to omit the comma
  341.  
  342.    dim ouDN, ouDNPath
  343.    ouDN = Mid(DN, p + 1)   ' + 1 to skip the comma
  344.    ouDNPath = "LDAP://" & DC & "/" & ouDN
  345.       
  346.    dim container, errnum3
  347.    set container = GetObject(ouDNPath)
  348.    select case Err.Number
  349.       case E_ADS_ERROR_DS_NO_SUCH_OBJECT
  350.          Bail "Container " & ouDN & " not found"
  351.       case 0
  352.          ' do nothing
  353.       case else
  354.          Echo "Error attempting to bind to " & ouDN
  355.          DumpErrAndQuit
  356.    end select
  357.  
  358.    dim dstObject
  359.    set dstObject = container.Create(classNames(objectClass), dstCN)
  360.    if Err.Number then
  361.       Echo "Error attempting to create " & DN
  362.       DumpErrAndQuit
  363.    end if
  364.  
  365.    dstObject.Put "samAccountName", samName
  366.    if Err.Number then
  367.       Echo "Error attempting to set samAccountName for " & DN
  368.       DumpErrAndQuit
  369.    end if
  370.  
  371.    select case objectClass
  372.       case CLASS_USER
  373.          ' nothing more to add
  374.  
  375.       case CLASS_LOCAL_GROUP
  376.          ' set group type to local
  377.          dstObject.Put "groupType", ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP + ADS_GROUP_TYPE_SECURITY_ENABLED
  378.          if Err.Number then
  379.             Echo "Error attempting to set local group type for " & DN
  380.             DumpErrAndQuit
  381.          end if
  382.  
  383.       case CLASS_GLOBAL_GROUP
  384.          ' set group type to global
  385.          dstObject.Put "groupType", ADS_GROUP_TYPE_GLOBAL_GROUP + ADS_GROUP_TYPE_SECURITY_ENABLED
  386.          if Err.Number then
  387.             Echo "Error attempting to set global group type for " & DN
  388.             DumpErrAndQuit
  389.          end if
  390.  
  391.    end select
  392.  
  393.    dstObject.SetInfo
  394.    if Err.Number then
  395.       Echo "Error attempting to commit create of " & DN
  396.       DumpErrAndQuit
  397.    end if
  398.  
  399.    Echo "Created " & DN
  400.  
  401.    set CreateDestinationDN = dstObject
  402. end function
  403.  
  404.  
  405.  
  406. ' for each group to which the source user object belongs, look for that
  407. ' group's sid in the sid histories of objects in the destination forest
  408. ' (domain?).  If found, add the destination user as a member of the located
  409. ' group.  Thus, when a user is cloned, the clone becomes a member of all the
  410. ' existing cloned groups corresponding to the original groups the
  411. ' orignal user belonged to.
  412.  
  413. sub FixupUserGroupMemberships(byref srcObject, byref dstObject, byval dstDC)
  414.    on error resume next
  415.    Echo "Fixing group memberships for " & dstObject.Class & " " & dstObject.Name
  416.  
  417.    nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC
  418.    if Err.Number then DumpErrAndQuit
  419.  
  420.    dim group
  421.    dim sidString
  422.    for each group in srcObject.Groups
  423.       if (ObjectClass(group) = CLASS_GLOBAL_GROUP) then
  424.          Echo "  Found global group " & group.ADsPath
  425.  
  426.          sid.SetAs ADS_SID_WINNT_PATH, group.AdsPath & "," & group.Class
  427.          if Err.Number then DumpErrAndQuit
  428.  
  429.          sidString = sid.GetAs(ADS_SID_SDDL)
  430.          if Err.Number then DumpErrAndQuit
  431.  
  432.          if IsBuiltInSid(sidString) then
  433.             Echo "  " & group.ADsPath & " is a built-in group"
  434.  
  435.             ' built-ins are present in every domain with the same sid.  So we
  436.             ' can't search for the corresponding destination object by sid, or
  437.             ' we may be multiple matches (if there is more than 1 domain in the
  438.             ' destination forest, and the destination DC also happens to be
  439.             ' a global catalog).  So, here we compose a sid-style LDAP path
  440.             ' for the built-in destination object.
  441.  
  442.             sidString = "<sid=" & sid.GetAs(ADS_SID_HEXSTRING) & ">"
  443.             if Err.Number then DumpErrAndQuit
  444.  
  445.             dim mypath
  446.             mypath = "LDAP://" & dstDC & "/" & sidString
  447.  
  448.             dim mygroup
  449.             set mygroup = GetObject(mypath)
  450.             if Err.Number then DumpErrAndQuit
  451.  
  452.             if not IsUserMemberOfGroup(mygroup, dstObject) then
  453.                Echo "  Adding " & dstObject.Name & " to group " & mygroup.Name
  454.                mygroup.Add dstObject.AdsPath
  455.             else
  456.                Echo "  " & dstObject.Name & " is already member of " & mygroup.Name
  457.             end if
  458.             if Err.Number then DumpErrAndQuit 
  459.          else
  460.  
  461.             ' find the DN of the object with that sid as its object sid or in
  462.             ' its sid history (the sid history is where it will be, if the object
  463.             ' is a clone).
  464.  
  465.             nameTranslate.Set ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, sidString
  466.             select case Err.Number
  467.                case E_ADS_ERROR_DS_NAME_NOT_FOUND
  468.                   ' do nothing: skip this member; it hasn't been cloned yet
  469.  
  470.                   Echo "  Skipping " & group.ADsPath & " -- not cloned yet"
  471.  
  472.                case 0
  473.                   ' found!
  474.                   dim foundDN
  475.                   foundDN = ""
  476.                   foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779)  ' aka full DN
  477.  
  478.                   select case Err.Number
  479.                      case E_ADS_ERROR_DS_NAME_NOT_FOUND
  480.                         ' do nothing: skip this member; it hasn't been cloned yet
  481.                      case 0
  482.                         AddUserToGroup dstObject, foundDN, dstDC
  483.                      case else
  484.                         DumpErrAndQuit
  485.                   end select
  486.  
  487.                case else
  488.                   DumpErrAndQuit
  489.  
  490.             end select
  491.          end if
  492.       else
  493.          Echo "  Skipping group " & group.AdsPath & " -- not global group"
  494.       end if
  495.  
  496.       ' need to clear this so next iteration won't choke.
  497.       Err.Clear
  498.    next
  499. end sub
  500.  
  501.  
  502.  
  503. ' for each member of the source local group, obtain the member's SID and add
  504. ' that SID as a member of the destination local group.  If that SID does not
  505. ' refer to a security principal in the destination domain, then the SAM will
  506. ' create a Foreign Principal Object (FPO) to represent that SID.  then SAM
  507. ' will replace the reference to the SID in the group membership with the DN
  508. ' of the FPO.  An FPO acts like a proxy for the SID.
  509.  
  510. sub CopyLocalGroupMembership(byref srcObject, byref dstObject)
  511.    on error resume next
  512.  
  513.    Echo "Copying local group membership"
  514.  
  515.    ' get the sids in string form of each of the members of the source
  516.    ' group.  collect them in an array
  517.    dim member
  518.    dim sidString
  519.    dim sidStringArray()
  520.    dim i
  521.    i = 0
  522.  
  523.    dim dn
  524.    dn = dstObject.Get("distinguishedName")
  525.    if Err.Number then DumpErrAndQuit
  526.  
  527.    Echo "  Getting destination group membership as SIDs"
  528.  
  529.    dim dstExistingMemberSIDs
  530.    dstExistingMemberSIDs = clonepr.GetMembersSIDs(dn)
  531.    if Err.Number then DumpErrAndQuit
  532.  
  533.    dim numExistingMembers
  534.    numExistingMembers = 0
  535.    dim x
  536.    for each x in dstExistingMemberSIDs
  537.      numExistingMembers = numExistingMembers + 1
  538.    next
  539.  
  540.    for each member in srcObject.Members
  541.       dim sidDeletedAccount
  542.       if IsDeletedAccount(member.AdsPath, sidDeletedAccount) then
  543.          Echo "  Considering deleted account: " & sidDeletedAccount
  544.          sid.SetAs ADS_SID_SDDL, sidDeletedAccount
  545.       else
  546.          Echo "  Considering normal account: " & member.AdsPath
  547.          sid.SetAs ADS_SID_WINNT_PATH, member.AdsPath & "," & member.Class
  548.       end if
  549.       if Err.Number then DumpErrAndQuit
  550.  
  551.       sidString = "<sid=" & sid.GetAs(ADS_SID_HEXSTRING) & ">"
  552.       if Err.Number then DumpErrAndQuit
  553.  
  554.       if (0 = numExistingMembers) Or (not SidStringExists(sidString, dstExistingMemberSIDs)) then
  555.          Echo "  Adding " & sidString
  556.          redim preserve sidStringArray(i)
  557.          sidStringArray(i) = sidString
  558.          i = i + 1
  559.       end if
  560.    next
  561.  
  562.    ' use the array to update the destination group in one whack.
  563.    if i then
  564.       if 0 = numExistingMembers then 
  565.         dstObject.PutEx ADS_PROPERTY_UPDATE, "member", sidStringArray
  566.       else
  567.         dstObject.PutEx ADS_PROPERTY_APPEND, "member", sidStringArray
  568.       end if
  569.       if Err.Number then DumpErrAndQuit
  570.  
  571.       dstObject.SetInfo
  572.       if Err.Number then DumpErrAndQuit
  573.    end if
  574. end sub
  575.  
  576.  
  577.  
  578. function IsDeletedAccount(byref AdsPath, byref sidDeletedAccount)
  579.   dim pos0, pos1
  580.   pos0 = InStr(1, AdsPath, "://", 1)
  581.   pos1 = InStr(pos0 + 3, AdsPath, "/", 1)
  582.  
  583.   if 0 = pos1 then
  584.     IsDeletedAccount = True
  585.     sidDeletedAccount = Mid(AdsPath, pos0 + 3)
  586.   else
  587.     IsDeletedAccount = False
  588.   end if
  589.  
  590. end function
  591.  
  592.  
  593.  
  594. function SidStringExists(byref sidString, byref dstExistingMemberSIDs)
  595.   dim sid
  596.   sid = UCase(sidString)
  597.  
  598.   SidStringExists = False
  599.  
  600.   dim x
  601.   For each x in dstExistingMemberSIDs
  602.     if UCase(x) = sid then
  603.        Echo "  Skipping existing sid " & x
  604.        SidStringExists = True
  605.        exit function
  606.     end if
  607.   next
  608.  
  609. end function
  610.  
  611.  
  612.  
  613. ' for each member of the source global group, look for that member's sid in
  614. ' the sid histories of objects the destination forest (domain?).  If found,
  615. ' add that located object as a member of the destination group.  Thus,
  616. ' when a global group is cloned, the existing clones of all users that belong
  617. ' to the original group will belong to the cloned group.
  618.  
  619. sub FixupGlobalGroupMembers(byref srcObject, byref dstObject, byval dstDC)
  620.    on error resume next
  621.    Echo "Fixing group membership for " & dstObject.Class & " " & dstObject.Name
  622.  
  623.    nameTranslate.Init ADS_NAME_INITTYPE_SERVER, dstDC
  624.    if Err.Number then DumpErrAndQuit
  625.  
  626.    dim member
  627.    dim sidString
  628.    for each member in srcObject.Members
  629.  
  630.       if member.UserFlags and UF_NORMAL_ACCOUNT then
  631.  
  632.          ' extract the sid of the account
  633.          sid.SetAs ADS_SID_WINNT_PATH, member.AdsPath & "," & member.Class
  634.          if Err.Number then DumpErrAndQuit
  635.  
  636.          sidString = sid.GetAs(ADS_SID_SDDL)
  637.          if Err.Number then DumpErrAndQuit
  638.  
  639.          ' find the DN of the member with that sid as its object sid or in
  640.          ' its sid history (the sid history is where it will be, if the member
  641.          ' is a clone).
  642.          nameTranslate.Set ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME, sidString
  643.          select case Err.Number
  644.             case E_ADS_ERROR_DS_NAME_NOT_FOUND
  645.                ' do nothing: skip this member; it hasn't been cloned yet
  646.  
  647.             case 0
  648.                ' found!
  649.                dim foundDN
  650.                foundDN = ""
  651.                foundDN = nameTranslate.Get(ADS_NAME_TYPE_1779)  ' aka full DN
  652.  
  653.                select case Err.Number
  654.                   case E_ADS_ERROR_DS_NAME_NOT_FOUND
  655.                      ' do nothing: skip this member; it hasn't been cloned yet
  656.                   case 0
  657.                      ' add the dn to the members property of the dst object
  658.                      dim path
  659.                      path = "LDAP://" & dstDC & "/" & foundDN
  660.                      Dim tempObj 
  661.                      set tempObj = GetObject(path)
  662.                      if Err.Number then DumpErrAndQuit
  663.                      if NOT IsUserMemberOfGroup( dstObject, tempObj ) then
  664.                         Echo "  adding " & foundDN & " to group " & dstObject.Name
  665.                         dstObject.Add path
  666.                      end if
  667.                      if Err.Number then DumpErrAndQuit
  668.  
  669.                   case else
  670.                      DumpErrAndQuit
  671.                end select
  672.  
  673.             case else
  674.                DumpErrAndQuit
  675.          end select
  676.  
  677.          ' need to clear this so the next iteration doesn't choke
  678.          Err.Clear
  679.  
  680.       else 
  681.  
  682.          ' skip computer, temp and trust accounts
  683.          Echo "  Skipping non-user account " & member.Name
  684.       end if
  685.    next
  686. end sub
  687.  
  688.  
  689.  
  690. ' user - in, reference to user object, bound with LDAP provider.
  691. ' groupDN - in, full DN of the group to which the user is to be added
  692. ' dstDC - in, name of destination domain controller
  693.  
  694. sub AddUserToGroup(byref user, byval groupDN, byval dstDC)
  695.    on error resume next
  696.  
  697.    dim path
  698.    path = "LDAP://" & dstDC & "/" & groupDN
  699.  
  700.    dim group
  701.    set group = GetObject(path)
  702.    if Err.Number then DumpErrAndQuit
  703.  
  704.    if not IsUserMemberOfGroup(group,user) then 
  705.       Echo "  Adding " & user.Name & " to group " & group.Name
  706.       group.Add user.AdsPath
  707.    else
  708.       Echo "  " & user.Name & " is already member of " & group.Name
  709.    end if
  710.    if Err.Number then DumpErrAndQuit
  711. end sub
  712.  
  713.  
  714.  
  715. function IsUserMemberOfGroup( byref group, byref user )
  716.    if group.IsMember(user.AdsPath) then
  717.         IsUserMemberOfGroup = True
  718.         exit function
  719.    end if 
  720.  
  721.    sid.SetAs ADS_SID_ACTIVE_DIRECTORY_PATH, group.AdsPath
  722.    if Err.Number then DumpErrAndQuit
  723.  
  724.    dim sidString
  725.     sidString = sid.GetAs(ADS_SID_SDDL)
  726.     if Err.Number then DumpErrAndQuit
  727.     if Len(sidString) > 9 then
  728.         dim lastDash
  729.         lastDash = InStrRev(sidString, "-", -1, 1)
  730.         if lastDash then
  731.            dim ridString
  732.            ridString = Mid(sidString, lastDash + 1)
  733.            if StrComp(ridString,user.PrimaryGroupId,1) = 0 then
  734.                 IsUserMemberOfGroup = True
  735.                 exit function
  736.            end if
  737.         end if 
  738.     end if
  739.     
  740.     IsUserMemberOfGroup = False
  741. end function
  742.     
  743.  
  744.  
  745. ' based on the class of the object, return one of CLASS_USER,
  746. ' CLASS_LOCAL_GROUP, CLASS_GLOBAL_GROUP, CLASS_OTHER
  747.  
  748. function ObjectClass(object)
  749.    dim cls
  750.    cls = UCase(object.Class)
  751.  
  752.    if cls = "GROUP" then
  753.       if (object.GroupType and ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP) then
  754.          ' type is local group
  755.          ObjectClass = CLASS_LOCAL_GROUP
  756.          exit function
  757.       else
  758.          if ((object.GroupType and ADS_GROUP_TYPE_GLOBAL_GROUP) or (object.GroupType and ADS_GROUP_TYPE_UNIVERSAL_GROUP)) then
  759.             ' type is global group
  760.             ObjectClass = CLASS_GLOBAL_GROUP
  761.             exit function
  762.          end if
  763.       end if
  764.    else
  765.       if cls = "USER" then
  766.          ' type is user
  767.          ObjectClass = CLASS_USER
  768.          exit function
  769.       end if
  770.    end if
  771.  
  772.    ' type is not recognized
  773.    ObjectClass = CLASS_OTHER
  774.    exit function
  775. end function
  776.  
  777.  
  778.  
  779. ' returns non-zero if the stringized SID refers to a well-known rid, zero
  780. ' otherwise
  781.  
  782. function HasWellKnownRid(byval sidString)
  783.    ' a SID refers to a well-known account if the first sub-authority (aka
  784.    ' RID) is < 1000.  The first subauthority is the last portion of the
  785.    ' stringized SID
  786.  
  787.    if Len(sidString) > 9 then
  788.       dim lastDash
  789.       lastDash = InStrRev(sidString, "-", -1, 1)
  790.       if lastDash then
  791.          dim ridString
  792.          ridString = Mid(sidString, lastDash + 1)
  793.          if CLng(ridString) < 1000 then
  794.             HasWellKnownRid = True
  795.             exit function
  796.          end if
  797.       end if
  798.    end if
  799.  
  800.    HasWellKnownRid = False
  801. end function
  802.  
  803.  
  804.  
  805. ' returns non-zero if the stringized SID refers to a builtin sid, zero
  806. ' otherwise
  807.  
  808. function IsBuiltInSid(byval sidString)
  809.    ' a SID  refers to builtin account or group if it has prefix S-1-5-32-
  810.  
  811.    if Len(sidString) > 9 then
  812.          dim  prefixString
  813.          prefixString = Mid(sidString, 1, 9)
  814.          if StrComp( prefixString, "S-1-5-32-", 1 ) = 0  then
  815.             IsBuiltInSid = true
  816.             exit function
  817.          end if
  818.    end if
  819.  
  820.    IsBuiltInSid = False
  821. end function
  822.  
  823.  
  824.  
  825. ' searches for and returns the value of a command line argument of the form
  826. ' /argName:value from the supplied array.  erases the entry in the array so
  827. ' that only untouched entries remain.
  828.  
  829. function GetArgValue(argName, args())
  830.     dim a
  831.     dim v
  832.     dim argNameLength
  833.     dim x
  834.     dim argCount
  835.     dim fullArgName
  836.  
  837.     fullArgName = "/" & argName & ":"
  838.     argCount = Ubound(args)
  839.  
  840.     ' Get the length of the argname we are looking for
  841.     argNameLength = Len(fullArgName)
  842.     GetArgValue = "" ' default to nothing
  843.     
  844.     for x = 0 To argCount 
  845.         if Len(args(x)) >= argNameLength then
  846.  
  847.             a = Mid(args(x), 1, argNameLength)
  848.             if UCase(a) = UCase(fullArgName) then
  849.  
  850.                 ' erase it so we can look for unknown args later
  851.                 v = args(x)
  852.                 args(x) = ""
  853.  
  854.                 if Len(v) > argNameLength then
  855.                     GetArgValue = Mid(v, argNameLength + 1)
  856.                     exit function
  857.                 else 
  858.                     GetArgValue = ""
  859.                     exit function
  860.                 end if
  861.             end if
  862.         end if
  863.     next 
  864. end function
  865.  
  866.  
  867.  
  868. ' walks thru the array searching for any non-empty element.  if at least one
  869. ' is found, then return non-zero.  Otherwise return 0.
  870.  
  871. function CheckForBadArgs(byref args())
  872.    dim i
  873.    for i = 0 to UBound(args) 
  874.       if Len(args(i)) > 0 then
  875.          CheckForBadArgs = 1
  876.          exit function
  877.       end if
  878.    next
  879.  
  880.    CheckForBadArgs = 0
  881. end function
  882.  
  883.  
  884.  
  885. sub DumpErrAndQuit
  886.    dim errnum
  887.    errnum = Err.Number
  888.  
  889.    Echo "Error 0x" & CStr(Hex(errnum)) & " occurred."
  890.    if len(Err.Description) then
  891.       Echo "Error Description: " & Err.Description
  892.    end if
  893.    if len(Err.Source) then 
  894.       Echo "Error Source     : " & Err.Source
  895.    end if
  896.    Echo "ADsError Description: "
  897.    Echo adsError.GetErrorMsg(errnum)
  898.    wscript.quit(0)
  899. end sub
  900.  
  901.  
  902.  
  903. sub Bail(byref message)
  904.    Echo "Error: " & message
  905.    wscript.quit(0)
  906. end sub
  907.  
  908.  
  909.  
  910. sub Echo(byref message)
  911.    wscript.echo message
  912. end sub
  913.  
  914.  
  915.  
  916. ' clonepr.vbi end
  917.  
  918.  
  919.  
  920.  
  921.  
  922.  
  923.  
  924.  
  925. const SCRIPT_FILENAME    = "cloneggu.vbs"
  926. const SCRIPT_SOURCE_NAME = "cloneggu.vbt"       
  927. const SCRIPT_DATE        = "Aug 17 2001"      
  928. const SCRIPT_TIME        = "12:42:14"      
  929.  
  930.  
  931.  
  932.  
  933.  
  934.  
  935.  
  936.  
  937.  
  938. ' clonedom.vbi start
  939.  
  940.  
  941.  
  942.  
  943.  
  944.  
  945.  
  946.  
  947.  
  948. const ARG_COUNT = 5
  949.  
  950. sub Main
  951.    if wscript.arguments.count <> ARG_COUNT then
  952.       PrintUsageAndQuit
  953.    end if
  954.  
  955.    ' copy the command-line arguments for parsing
  956.    dim args()
  957.    Redim args(0)
  958.    args(0) = ""
  959.  
  960.    dim i
  961.    for i = 0 to wscript.arguments.count - 1
  962.        Redim Preserve args(i)
  963.        args(i) = wscript.arguments.item(i)
  964.    next
  965.  
  966.    ' command line parameters
  967.    dim srcDC       ' source domain controller                     
  968.    dim srcDom      ' source domain                                
  969.    dim dstDC       ' destination controller                       
  970.    dim dstDom      ' destination domain                           
  971.    dim dstOU       ' destination OU for clones
  972.  
  973.    ' parse the saved command-line arguments, extracting the values
  974.    srcDC   = GetArgValue("srcdc",   args)
  975.    srcDom  = GetArgValue("srcdom",  args)
  976.    dstDC   = GetArgValue("dstdc",   args)
  977.    dstDom  = GetArgValue("dstdom",  args)
  978.    dstOU   = GetArgValue("dstou",   args)
  979.  
  980.    ' ensure the user did not pass any unrecognized command-line arguments
  981.    if CheckForBadArgs(args) then
  982.        Echo "Unknown command-line arguments specified"
  983.        PrintUsageAndQuit
  984.    end if
  985.    
  986.    ' establish authenticate connections to the source and destination domain
  987.    ' controllers
  988.    on error resume next
  989.    clonepr.Connect srcDC, srcDom, dstDC, dstDom
  990.    if Err.Number then DumpErrAndQuit
  991.  
  992.    Echo "Connected to source and destination domain controllers"
  993.  
  994.    dim srcDomain
  995.    set srcDomain = GetObject("WinNT://" & srcDom & "/" & srcDC & ",Computer")
  996.    if Err.Number then DumpErrAndQuit
  997.  
  998.    ' for every security principal in the source domain, call
  999.    ' ShouldCloneObject. if that function returns True, then clone the object.
  1000.    ' Otherwise ignore it.
  1001.    dim cloneCounter
  1002.    dim srcObject
  1003.    dim srcObjectClass
  1004.    cloneCounter = 0
  1005.    for each srcObject in srcDomain
  1006.       if ShouldCloneObject(srcObject) then
  1007.          Echo "Bound to source " & srcObject.Class & " " & srcObject.Name
  1008.  
  1009.          srcObjectClass = ObjectClass(srcObject)
  1010.  
  1011.          do 
  1012.             if srcObjectClass = CLASS_USER then
  1013.                if srcObject.UserFlags and UF_TEMP_DUPLICATE_ACCOUNT then
  1014.                   Echo "Skipping temporary local user account."
  1015.                   exit do
  1016.                end if
  1017.             end if 
  1018.  
  1019.             dim srcSam        ' source principal SAM name
  1020.             dim dstSam        ' destination principal SAM name
  1021.             dim dstDN         ' destination principal full DN
  1022.  
  1023.             srcSam = srcObject.Name
  1024.             dstSam = srcSam
  1025.             dstDN = adsPathname.GetEscapedElement(0, "CN=" & dstSam) & "," & dstOU
  1026.  
  1027.             CloneSecurityPrincipal srcObject, srcSam, dstDom, dstDC, dstSam, dstDN
  1028.             cloneCounter = cloneCounter + 1
  1029.          loop while 0
  1030.  
  1031.          Echo ""
  1032.  
  1033.       end if
  1034.    next
  1035.  
  1036.    Echo cloneCounter & " objects(s) cloned"
  1037. end sub
  1038.  
  1039.  
  1040.  
  1041. sub PrintUsageAndQuit
  1042.    Echo "Usage: cscript " & SCRIPT_FILENAME & " /srcdc:<dcname> /srcdom:<domain>"
  1043.    Echo "/dstdc:<dcname> /dstdom:<domain> /dstou:<ouname>"
  1044.    Echo ""
  1045.    Echo "Parameters:"
  1046.    Echo " /srcdc   - source domain controller NetBIOS computer name (without leading \\)"
  1047.    Echo ""
  1048.    Echo " /srcdom  - source domain NetBIOS name"
  1049.    Echo ""
  1050.    Echo " /dstdc   - destination domain controller NetBIOS computer name (without "
  1051.    Echo "            leading \\)"
  1052.    Echo "            This script must be run on the machine indicated here."
  1053.    Echo ""
  1054.    Echo " /dstdom  - destination domain DNS name"
  1055.    Echo ""
  1056.    Echo " /dstou   - destination OU for the clones"
  1057.    Echo ""
  1058.    Echo "Notes:"
  1059.    Echo ""
  1060.    Echo "If the destination principals do not exist, they will be created."
  1061.    Echo "In that case, the OU named by dstou must exist."
  1062.    Echo ""
  1063.    Echo "Currently logged-on user must be a member of the Administrators"
  1064.    Echo "group of both the source and destination domains."
  1065.    Echo ""
  1066.    Echo SCRIPT_DATE & " " & SCRIPT_TIME
  1067.  
  1068.    wscript.quit(0)
  1069. end sub
  1070.  
  1071.  
  1072.  
  1073. ' clonedom.vbi end
  1074.  
  1075.  
  1076.  
  1077.  
  1078. Main
  1079. wscript.quit(0)
  1080.  
  1081.  
  1082.  
  1083. ' returns True if the object is a global group and not a well-known account
  1084.  
  1085. function ShouldCloneObject(byref srcObject)
  1086.    on error resume next
  1087.  
  1088.    dim srcObjectClass
  1089.    srcObjectClass = ObjectClass(srcObject)
  1090.    if srcObjectClass = CLASS_GLOBAL_GROUP OR srcObjectClass = CLASS_USER then
  1091.  
  1092.       ' determine if the object is well-known
  1093.  
  1094.       sid.SetAs ADS_SID_WINNT_PATH, srcObject.AdsPath & "," & srcObject.Class
  1095.       if Err.Number then DumpErrAndQuit
  1096.  
  1097.       dim sidString
  1098.       sidString = sid.GetAs(ADS_SID_SDDL)
  1099.       if Err.Number then DumpErrAndQuit
  1100.       'To Stop Cloning Well Known Sids Uncomment 4 lines below
  1101.  
  1102.      ' if HasWellKnownRid(sidString) then
  1103.      '    ShouldCloneObject = False
  1104.      '    exit function
  1105.      ' end if
  1106.  
  1107.       if IsBuiltInSid( sidString ) then
  1108.          Echo srcObject.Name & " is a builtin Account."
  1109.          Echo "BuiltIn Users and Groups cannot be cloned"
  1110.          ShouldCloneObject = False
  1111.          exit function
  1112.       end if
  1113.  
  1114.  
  1115.       ShouldCloneObject = True
  1116.       exit function
  1117.    end if
  1118.  
  1119.    ShouldCloneObject = False
  1120. end function
  1121.  
  1122.  
  1123.  
  1124. ' cloneggu.vbt end
  1125.