フォレスト間の連絡先を同期する

あらゆる組織の活動の過程で、一時的な同盟が生じ、消滅し、友人を作り、パートナーとの友情を破り、再編成され、他の組織を吸収し、逆もまた同様に、いくつかに分かれます。 ITの観点からは、多くの場合、共有リソースの作成、Active Directoryフォレスト間の信頼関係、ネットワーク間VPNトンネルの確立、およびその他の「統一」手順が必要になります。 以下に、別の信頼できるドメインのユーザーに関する連絡先情報をコピーする、そのような手順の1つを検討します。



ユーザーが「手紙を受け取らない」と誓うことがあります。 なぜそう はい、彼らは1つのアドレスに送信し、別のアドレスが関連しているのは、女の子が結婚し、名前を変更し、優秀な管理者が以前のメールアドレスを保存しなかったためです。 従業員は「ドルのように」ではなく、電話の受け手に「Es!」と言って間違えることがあります。また、反対側の対談者はラテン文字「C」を入力して間違えます。 ユーザーは.ruの代わりに.comを、.comの代わりに.ruを書くことができます。多くのユーザーがいる場合、そのようなエラーは苦情の終わりのないストリームに変わります。 あらゆる種類の愚かさはスキルによって対立する必要があるため、組織のすべてのパートナーの最新のアドレス帳が必要になります。



構造について


私たちの組織では、Exchangeサーバーをメールシステムとして使用し、Outlook電子メールを送信するためのクライアントソフトウェアとして、従業員がパートナーの連絡先情報をアドレス帳で確認できるように、Active Directoryに対応する連絡先オブジェクトを作成するだけで十分です。 パートナーは好きなものを使用し、一部はロータスドミノに座っており、一部はsendmailを送信しており、一部はKerio Mail Serverで作業していますが、それらはすべて同じです:各Active Directoryユーザーはメール属性に有効な電子メールアドレスを持っています。 だから彼らはどうやってそれをするのか、聞かないで、どういうわけか彼らはそれをする。 もしそうなら、これは「彼らのユーザー」を「私たちの連絡先」に変えることで使用できます。







信頼関係のおかげで、ユーザーを自分のユーザーとして読み取ることができます(もちろん、 選択認証が信頼で有効になっていない場合、この場合はもう少し難しくなります)。また、ドメインに書き込む権利があります。

一般に、輸出入手続きには、たとえばForefront Identity Managerを使用できますが、第一に、私たちは経済的な人々であり、お金を捨てません。さらに、PowerShellは、ADPowerShellに支障のない互換性のために、.NETクラスのSystem.DirectoryServices.DirectoryEntrySystem.DirectoryServices.DirectorySearcherで十分です。



方法論


Active Directoryで必要なすべてのユーザーを検索した後、それらのプロパティをメモリにコピーして、配列を埋めます。 PowerShellを使用すると、 Add-Memberコマンドレットを使用して、文字通りその場で既存のオブジェクトにプロパティを追加できます。これを使用します。 その結果、適切に構造化されたリストが得られます。さらに、その後、別のコマンドレットWhere-Objectを使用して簡単に検索できます。 また、外部連絡先コンテナにあるすべての連絡先をメモリにコピーし、2つのリストを比較します。 比較の結果は、3つの配列になります。新しい連絡先、削除する連絡先、および一部のプロパティのみが変更された連絡先です。 最初の起動後、見つかったすべての連絡先が新しいことは明らかです。

連絡先を初めてインポートした後は、「すべてを削除して再度入力」メソッドではなく、変更されたフィールドのみを変更して、関連性に注意する必要があります。 なぜそう はい。これは、まずExchange自体がデリケートなものであり、アドレス帳の作成に時間がかかるためです。次に、ウィザードのRIDが失敗すると、Active Directoryでオブジェクトをバッチ作成しようとすると不快な事態が発生する可能性があります。 各Active Directoryオブジェクトは、オブジェクトの一意の識別子を格納するためにobjectGUID属性を使用し、システムがオブジェクトを作成するときに設定され、変更されることはありません。それを記憶し、16進文字列に変換して連絡先情報フィールドに格納します。 ただし、他の目的で情報を使用する場合、他の属性に識別子を格納できます。 信じてください 、それで十分です。

また、 userCertificate属性についても忘れないでください。「contact」クラスのオブジェクトの場合、 Active Directoryユーザーとコンピュータースナップインには表示されませんが、Outlookはそれを使用して、暗号化/署名された電子メールを送受信します。 もちろん、このビジネスは、ある組織が別の組織の証明書を信頼している場合にのみ機能しますが、この問題はこのトピックの範囲外です。 注意が必要な別の属性はotherTelephoneと呼ばれます 。 多値であるという点で興味深いため、特別な関係があります。 実際、このような属性は多数ありますが、スクリプトで使用されるのは1つだけです。したがって、複数行の属性の抽出/書き込み専用のプロシージャを作成することは、将来スクリプトを変更するための一種のスコープです。

そして、もちろん、私たちは安全性を覚えています。 「連絡先」クラスのオブジェクトの作成、変更、削除を特定のアカウントおよび特定のコンテナのみに委任することをお勧めします。この場合、潜在的な攻撃者はスクリプトでマシンの制御を取得しているため、特に深刻なことはできません。 そして今、コメント付きのスクリプト:



#      #   #      function Write-LogFile([string]$logFileName) { Process { $_ $dt = Get-Date $str = $dt.DateTime + " " + $_ $str | Out-File -FilePath $logFileName -Append } } #  ,     #  ArrayList,      # ,  ,     function Compare-ArrayLists([System.Collections.ArrayList] $ListA, [System.Collections.ArrayList] $ListB) { if ($ListA.Count -ne $ListB.Count) { return $false } else { $CompListA = New-Object System.Collections.ArrayList($null) $CompListB = New-Object System.Collections.ArrayList($null) for ($i=0;$i -lt $ListA.Count;$i++) { if ($ListA[$i].GetType() -ne [String]) { $rc = $CompListA.Add([System.BitConverter]::ToString($ListA[$i])) } else { $rc = $CompListA.Add($ListA[$i]) } if ($ListB[$i].GetType() -ne [String]) { $rc = $CompListB.Add([System.BitConverter]::ToString($ListB[$i])) } else { $rc = $CompListB.Add($ListB[$i]) } } for ($i=0;$i -lt $CompListA.Count;$i++) { if ($CompListB.IndexOf($CompListA[$i]) -lt 0) {return $false} } return $true } } #      ,   (),   (). : #   #   # , ,    Exchange #     # ,   :    function Load-FromDomain([string] $DomainName, [string] $UnitName, [bool]$flagExchangeDomain, [ref]$A_Entries, [bool]$flagContacts) { if (!$flagContacts) { if (!$flagExchangeDomain) { #   LDAP,     ,    #    .   !userAccountControl:1.2.840.113556.1.4.803:=2, #   ,        # ,      (,        AD ) $strFilter = "(&(objectClass=user)(!objectClass=computer)(mail=*)(company=*))"#(!userAccountControl:1.2.840.113556.1.4.803:=2) } else { #  Exchange-   msExchHideFromAddressLists,      $strFilter = "(&(objectClass=user)(!objectClass=computer)(mail=*)(company=*)(!msExchHideFromAddressLists=TRUE))" #(!userAccountControl:1.2.840.113556.1.4.803:=2) } } else { $strFilter = "(&(objectClass=contact))" } $objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$DomainName+"/"+$UnitName) $objSearcher = New-Object System.DirectoryServices.DirectorySearcher $objSearcher.SearchRoot = $objDomain $objSearcher.PageSize = 1000 $objSearcher.Filter = $strFilter $objSearcher.SearchScope = "Subtree" #   ,   ,   , #    -    . $colProplist = "employeeID", "employeeType", "objectGUID", "CN", "givenName", "name", "sn", "legacyExchangeDN", "displayName", "mail", "wWWHomePage", "l", "postalCode", "initials", "physicalDeliveryOfficeName", "st", "streetAddress", "ipPhone", "title", "mobile", "department", "pager", "homePhone", "facsimileTelephoneNumber", "userAccountControl", "distinguishedName", "company", "Description", "otherTelephone", "telephoneNumber", "userCertificate" if ($flagExchangeDomain) { $colProplist += "mailNickname" $colProplist += "msExchHideFromAddressLists" } if ($flagContacts) { $colProplist += "info" } foreach ($i in $colPropList) { $rc = $objSearcher.PropertiesToLoad.Add($i) } $colResults = $objSearcher.FindAll() $colResults.Count foreach ($objResult in $colResults) { $objItem = $objResult.Properties #    ,     ,  ,    #   ,      . $Entry = New-Object -TypeName System.Object if (!$flagContacts) { $Entry | Add-Member -type NoteProperty -name "GUID" -Value ([System.BitConverter]::ToString($objItem.objectguid[0])).Replace('-','') } else { $Entry | Add-Member -type NoteProperty -name "GUID" -Value ([string]$objItem.info) } #    ,      , ..   . #  , ,  - ,      , #       ,         $UserProperties = $colProplist | Where-Object {($_ -ne "objectGUID") -and ($_ -ne "userCertificate") -and ($_ -ne "otherTelephone")} foreach ($UserProperty in $UserProperties) { if ($objItem.Item($UserProperty) -ne $null) { $Entry | Add-Member -type NoteProperty -name $UserProperty -Value ([string]$objItem.Item($UserProperty)) } } #   ,        if ($objItem.usercertificate -ne $null) { $Certificates = New-Object System.Collections.ArrayList($null) foreach ($Certificate in $objItem.usercertificate) { $rc = $Certificates.Add($Certificate) } $Entry | Add-Member -type NoteProperty -name "userCertificate" -Value ($Certificates) } if ($objItem.othertelephone -ne $null) { $Telephones = New-Object System.Collections.ArrayList($null) foreach ($Telephone in $objItem.othertelephone) { $rc = $Telephones.Add($Telephone) } $Entry | Add-Member -type NoteProperty -name "otherTelephone" -Value ($Telephones) } $A_Entries.Value += $Entry } } #  ,    . #            ,  : #    #    #    #    #    $A_Users = $A_Contacts = $A_NewContacts = $A_ChangedContacts = $A_ContactsToDelete = @() $LogFileName = "./GetUserContacts.log" $flagExchangeOrganization = $false #         #      : # ("< >","<>") ("< >","<  >","< exchange>") ("< 1>","< >","< Exchange>") ("< 2>"... #  # ("admin@litware.inc","password") ("litware.inc","ou=contacts,dc=litware,dc=inc",$true) ("contoso.com","dc=contoso,dc=com",$false) if ($args.Count -lt 3) { break } $UserName = $args[0][0] $Password = $args[0][1] $UserName | Write-LogFile $LogFileName $Password | Write-LogFile $LogFileName $Domain = $args[1][0] $ContactsOU = $args[1][1] $flagExchangeOrganization = $args[1][2] $Domain | Write-LogFile $LogFileName $ContactsOU | Write-LogFile $LogFileName $flagExchangeOrganization | Write-LogFile $LogFileName Load-FromDomain $Domain $ContactsOU $flagExchangeOrganization ([ref]$A_Contacts) $true for ($i=2;$i -lt $args.Count;$i++) { $SrcDomain = $args[$i][0] $SrcOU = $args[$i][1] $SrcExchangeFlag = $args[$i][2] $SrcDomain | Write-LogFile $LogFileName $SrcOU | Write-LogFile $LogFileName $SrcExchangeFlag | Write-LogFile $LogFileName Load-FromDomain $SrcDomain $SrcOU $SrcExchangeFlag ([ref]$A_Users) $false } #   ,    , #    ,     , #   .   ,    #  - ,       #      . foreach ($User in $A_Users) { $Contact = $A_Contacts | Where-Object {$_.GUID -eq $User.GUID} if ($Contact -eq $null) { $A_NewContacts += $User $A_NewContacts[$A_NewContacts.Length-1].distinguishedName = "" } else { $flagContactAdded = $false $UserProperties = ($User | Get-member -MemberType NoteProperty | Where-Object {($_.Name -ne "distinguishedName") -and ($_.Name -ne "mailNickname")}) foreach ($UserProperty in $UserProperties) { if ($Contact.($UserProperty.Name) -ne $null) { if ($User.($UserProperty.Name).GetType() -ne [System.Collections.ArrayList]) { if ($User.($UserProperty.Name) -ne $Contact.($UserProperty.Name)) { if (!$flagContactAdded) { $NewEntry = New-Object -TypeName System.Object $NewEntry | Add-Member -type NoteProperty -name "distinguishedName" -Value ($Contact.distinguishedname) $flagContactAdded = $true } $NewEntry | Add-Member -type NoteProperty -name ($UserProperty.Name) -Value $User.($UserProperty.Name) } } else { if (!(Compare-ArrayLists $User.($UserProperty.Name) $Contact.($UserProperty.Name))) { if (!$flagContactAdded) { $NewEntry = New-Object -TypeName System.Object $NewEntry | Add-Member -type NoteProperty -name "distinguishedName" -Value ($Contact.distinguishedname) $flagContactAdded = $true } $NewEntry | Add-Member -type NoteProperty -name ($UserProperty.Name) -Value $User.($UserProperty.Name) } } } } if ($flagContactAdded) { $A_ChangedContacts += $NewEntry } } } #    ,       foreach ($Contact in $A_Contacts) { $User = $A_Users | Where-Object {$_.GUID -eq $Contact.GUID} if ($User -eq $null) { $A_ContactsToDelete += $Contact } } # $A_NewContacts # $A_ChangedContacts # $A_ContactsToDelete #   foreach ($Contact in $A_ContactsToDelete) { $ContactsOUDN = "LDAP://" + $Domain + "/" + $ContactsOU $objContactsOU = new-object System.DirectoryServices.DirectoryEntry($ContactsOUDN, $Username, $Password, [System.DirectoryServices.AuthenticationTypes]::Secure) $objContactsOU.Delete("contact", $Contact.distinguishedName.Split(",")[0]) "" + $Contact.name | Write-LogFile $LogFileName } #   foreach ($Contact in $A_NewContacts) { $ContactsOUDN = "LDAP://" + $Domain + "/" + $ContactsOU $objContactsOU = new-object System.DirectoryServices.DirectoryEntry($ContactsOUDN, $Username, $Password, [System.DirectoryServices.AuthenticationTypes]::Secure) $NewContact = $objContactsOU.Children.Add("CN="+$Contact.CN,"contact") $NewContactProperties = ($Contact | Get-member -MemberType NoteProperty | Where-Object {($_.Name -ne "distinguishedName") ` -and ($_.Name -ne "GUID") -and ($_.Name -ne "CN") -and ($_.Name -ne "otherTelephone")` -and ($_.Name -ne "name") -and ($_.Name -ne "userAccountControl") -and ($_.Name -ne "userCertificate")}) if ($NewContactProperties -ne $null) { foreach ($NewContactProperty in $NewContactProperties) { $NewContact.Put($NewContactProperty.Name,$Contact.($NewContactProperty.Name)) } } if ($Contact.mail -ne $null) { if ($flagExchangeOrganization) { $NewContact.Put("targetAddress", "SMTP:" + $Contact.mail) $NewContact.Put("mailNickname", $Contact.mail.Split("@")[0]) $NewContact.Put("msExchPoliciesExcluded", "{26491CFC-9E50-4857-861B-0CB8DF22B5D7}") } $NewContact.Put("proxyAddresses", "SMTP:" + $Contact.mail) } if ($Contact.userCertificate -ne $null) { $NewContact.PutEx(2, "userCertificate", [Array]$Contact.userCertificate) } if ($Contact.otherTelephone -ne $null) { $NewContact.PutEx(2, "otherTelephone", [Array]$Contact.otherTelephone) } $NewContact.Put("info",$Contact.GUID) $NewContact.SetInfo() " " + $Contact.name | Write-LogFile $LogFileName } # , ,  ,  . #        ,    #    .   ,     CN (canonical name) #  ,        Rename() foreach ($Contact in $A_ChangedContacts) { $ContactDN = "LDAP://" + $Domain + "/" + $Contact.distinguishedName $ChangedContact = new-object System.DirectoryServices.DirectoryEntry($ContactDN, $Username, $Password, [System.DirectoryServices.AuthenticationTypes]::Secure) $ChangedContactProperties = ($Contact | Get-member -MemberType NoteProperty | Where-Object {($_.Name -ne "distinguishedName") ` -and ($_.Name -ne "GUID") -and ($_.Name -ne "CN") -and ($_.Name -ne "otherTelephone") ` -and ($_.Name -ne "name") -and ($_.Name -ne "userAccountControl") -and ($_.Name -ne "userCertificate")}) if ($ChangedContactProperties -ne $null) { foreach ($ChangedContactProperty in $ChangedContactProperties) { $ChangedContact.Put($ChangedContactProperty.Name,$Contact.($ChangedContactProperty.Name)) "  " + $ChangedContactProperty.Name + "  " + $Contact.distinguishedName | Write-LogFile $LogFileName } } if ($Contact.userCertificate -ne $null) { $ChangedContact.PutEx(1, "userCertificate", 0) $ChangedContact.PutEx(2, "userCertificate", [Array]$Contact.userCertificate) "  userCertificate  " + $Contact.distinguishedName | Write-LogFile $LogFileName } if ($Contact.otherTelephone -ne $null) { $ChangedContact.PutEx(1, "otherTelephone", 0) $ChangedContact.PutEx(2, "otherTelephone", [Array]$Contact.otherTelephone) "  otherTelephone  " + $Contact.distinguishedName | Write-LogFile $LogFileName } if ($Contact.CN -ne $null) { " " + $ChangedContact.distinguishedName + "   " + $Contact.CN | Write-LogFile $LogFileName $ChangedContact.Rename("CN="+$Contact.CN) } $ChangedContact.SetInfo() }
      
      







次に、コマンドラインを作成し、スクリプトのスケジューラでスケジュールするだけです。 もちろん、パートナーに連絡先が必要な場合は、もちろんこのスクリプトをパートナーと共有できます。



All Articles