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

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