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 / clonegg.vbs < prev    next >
Text File  |  2001-08-17  |  33KB  |  1,121 lines

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