本文為機器翻譯文章。如需檢視英文版,請選取 [原文] 核取方塊。您也可以將滑鼠指標移到文字上,即可在快顯視窗顯示英文原文。
譯文
原文

宣告模式驗證對服務應用程式的影響

SharePoint 2013
 

適用版本:SharePoint Server 2013

上次修改主題的時間:2015-03-09

摘要:了解如何在 SharePoint 2013 的服務應用程式上將 Windows 宣告驗證移轉至 SAML 型驗證。

本文說明如何在服務應用程式上將 Windows 宣告移轉至 SAML 型驗證。

本文內容:

本節說明的步驟是有關如何將 Microsoft Business Connectivity Services 和 Secure Store Service 資料庫中所儲存的 Windows 宣告身分識別移轉至 SAML 型身分識別。

開始移轉之前,請先檢閱下列必要條件的相關資訊:

  • 已安裝具有 2014 年 4 月累積更新 (CU) 的 SharePoint 2013。

  • SharePoint Server 2013。

  • Business Connectivity Services (BCS) 和 Secure Store 存在且具有 NT 驗證或 Windows 宣告身分識別。

  • 目標環境設定成使用支援的 SAML 宣告型身分識別提供者。

  • 移轉將會使用伺服器陣列管理員認證予以執行。

  • Migrate-BcsSssClaims.ps1 指令碼會移轉 Business Connectivity Services 資料庫和 Secure Store 資料庫中所儲存的現有 Windows 宣告身分識別。

若要移轉 Business Connectivity Services 資料庫和 Secure Store 資料庫中所儲存的現有 Windows 宣告型身分識別,請執行下列動作:

  1. 透過使用 Microsoft SQL Server Management Studio,來備份現有 BCS 和 Secure Store Service 資料庫。資料庫備份和還原是還原失敗移轉的主要方法。

  2. 建立移轉略過清單和自訂對應:

    1. 使用 Microsoft PowerShell ISE 或 [記事本] 這類編輯器,開啟 Migrate-BcsSssClaims.ps1 指令檔。

    2. 尋找 $skipMigrationList 變數。它包含必須明確地不移轉的主體。請使用逗號隔開多個主體。以下是範例:$skipMigrationList = @("some_domain\do_not_migrate_account1", "some_domain\do_not_migrate_account2")

    3. 尋找 $customMapping 變數。自訂對應會將身分識別明確地轉換為您指定的 SAML 程式碼。下列範例會將名稱為 old_domain\old_username1 的使用者身分識別移轉為 SAML 身分識別 username1@company.com,以及將 old_domain\old_username2 移轉為 username2@partner.com

      $customMapping = @{ "old_domain\old_username1" = "username1@company.com"; "old_domain\old_username2" = "username2@partner.com"; }

  3. Migrate-BcsSssClaims.ps1 指令碼會將 Business Connectivity Services 和 Secure Store 應用程式中的所有使用者和群組主體都移轉至 SAML 宣告型驗證提供者。

    使用 Windows PowerShell 執行 Migrate-BcsSssClaims 指令碼
    1. 確認符合下列成員資格:

      • SQL Server 執行個體上的 securityadmin 固定伺服器角色。

      • 待更新之所有資料庫上的 db_owner 固定資料庫角色。

      • 您正在執行 Windows PowerShell Cmdlet 之伺服器上的 Administrators 群組。

      • 可使用 Add-SPShellAdmin Cmdlet 來授與權限的管理員。

      注意事項 附註:
      如果您不具備上述權限,請連絡安裝程式系統管理員或 SQL Server 系統管理員來要求權限。 如需 Windows PowerShell 權限的其他資訊,請參閱<Add-SPShellAdmin>。
    2. 注意事項 附註:
      下列指令檔 (即 Migrate-BcsClaims.ps1Migrate-SSSClaims.ps1Migrate-Helpers.ps1) 需要複製並儲存至與 Migrate-BcsSssClaims.ps1 檔案相同的位置。執行 Migrate-BcsSssClaims.ps1 檔案時會使用它們。
    3. 複製下列程式碼,並將其貼入 [記事本] 這類編輯器中,然後將檔案儲存為 Migrate-BcsClaims.ps1

      #Description: Migrates an existing Windows claims stored in a Business Connectivity Service database.
      <#This script is used to migrate the identities stored in a Business Connectivity Services database to a new one (which depends on the parameter of MigrationCallback).
      
      Steps:
      * Get the database connection by using the Get-SPDatabase cmdlet.
      1. Retrieve the Identities in the table.
      2. For each Identity, convert it using the callback.
      3. Update the Identity with the new value.
      #>
      
      #return: the new value
      function Migrate-BcsClaims()
      {
      # Do we need the subscription ID and site parameters here?
         [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
          param(
              # The site subscription ID of the tenant whose user and group principals in the Managed
              # Metadata service applications are to be migrated to the new authentication model.
              [Parameter(Mandatory = $true, ParameterSetName = "tenant", ValueFromPipeline = $true, HelpMessage = "What is the site subscription ID of the tenant you want to migrate?")]
              [ValidateNotNull()]
              [Microsoft.SharePoint.PowerShell.SPSiteSubscriptionPipeBind] $SiteSubscriptionId,
      
              # A site collection whose user and group principals in the Managed Metadata service
              # application are to be migrated to the new authentication model. If the site collection
              # has a site subscription ID, use the -SiteSubscriptionId parameter instead.
              [Parameter(Mandatory = $true, ParameterSetName = "site", ValueFromPipeline = $true, HelpMessage = "Which site collection do you wish to migrate?")]
              [ValidateNotNull()]
              [ValidateScript({ $Site.SiteSubscription -eq $null })]
              [Microsoft.SharePoint.PowerShell.SPSitePipeBind] $Site,
      
              # The Managed Metadata web service to be migrated.
              [Parameter(Mandatory = $true)]
              [ValidateNotNull()]
              [Microsoft.SharePoint.BusinessData.SharedService.BdcServiceApplication] $WebServiceApplication,
      
              # An IMigrateEntityCallback object that performs the entity transformation.
              #[Parameter(Mandatory = $true)]
              #[ValidateNotNull()]
              [Microsoft.SharePoint.Administration.Claims.IMigrateEntityCallback] $Migrator,
      
              # This parameter prevents the original principals from being deleted or overwritten in the
              # database when the migrated principals are written. By default this parameter is false
              # and the original principals are deleted. This parameter is useful for a phased migration
              # where it is necessary to keep the Managed Metadata service application functioning for
              # users connecting from both migrated and non-migrated tenants. After all tenants
              # have been migrated, the original principals can be deleted by re-running this script
              # with this parameter disabled. Note that for testing migrations, the -WhatIf parameter can
              # also be used to help ensure that no changes are committed to the database.
              [bool] $IfRetainOldPrincipals = $false
              )
      
      
          #
          # Convert the old Identity to new one (the old one is Windows Claim type)
          #
          function ConvertOneIdentity([System.String]$oldValue)
          {
              if ($Migrator -eq $null)
              {
                  Write-Log "No Migrator!"
                  $null
              }
              else
              {
                  $migrationEntity = New-Object Microsoft.SharePoint.Administration.SPMigrationEntity $oldValue
                  
                  $result = $Migrator.ConvertEntity($script:migrationContext, $migrationEntity)
                  
                  if ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Success)
                  {
                      $newValue = $migrationEntity.MigratedName
                      return $newValue
                  }
                  elseif ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Failure)
                  {
                      Write-Log "Error: cannot convert $oldValue"
                  }
                  elseif ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::AlreadyMigrated)
                  {
                      Write-Log "Already been migrated: $oldValue"
                  }
                  elseif ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Skipped)
                  {
                      Write-Log "Skipped to migrate: $oldValue"
                  }
              }
      
              return $null
          }
          
          #
          # Start of main logic
          #
          $script:migrationContext = New-Object Microsoft.SharePoint.Administration.Claims.SPClaimMigrationContext($WebServiceApplication.Service)
          
      
          #
          # In MinDB we will use Content Database, not the database for the service application.
          # 
          #
          $db = $WebServiceApplication.Database
          if ($db -eq $null)
          {
              Write-Log "Cannot find BDC database".
              return
          }
      
          # default BDC schema is 'dbo', but in MinDB we will use 'bcs'
          $dbSchema = "dbo" 
          $sqlConnStr = $db.DatabaseConnectionString
      
          $sqlReadStmt = "SELECT * FROM [$dbSchema].[AR_MetadataObjectSecurity]"
          $sqlUpdateStmt = "UPDATE [$dbSchema].[AR_MetadataObjectSecurity] SET IdentityName=@newIdentityValue WHERE ID=@id"
          $sqlInsertStmt = "INSERT INTO [$dbSchema].[AR_MetadataObjectSecurity] (MetadataObjectId, IdentityName, DisplayName, RawSid, Rights, SettingId) VALUES (@metadataObjectId, @newIdentityValue, @displayName, @rawSid, @rights, @settingId)"
          
          $connection = New-Object System.Data.SqlClient.SqlConnection $sqlConnStr
          $connection.Open()
                      
          # To read existing data
          $sqlReadCmd = $connection.CreateCommand()
          $sqlReadCmd.CommandText = $sqlReadStmt
      
          $SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
          $SqlAdapter.SelectCommand = $SqlReadCmd
      
          $DataSet = New-Object System.Data.DataSet
          $SqlAdapter.Fill($DataSet)
          # Read ends; 
      
          $sqlUpdateCmd = New-Object System.Data.SqlClient.SqlCommand
          $sqlUpdateCmd.Connection = $connection
          
          if ($IfRetainOldPrincipals -eq $false)
          {
              $sqlUpdateCmd.CommandText = $sqlUpdateStmt
          }
          else
          {
              $sqlUpdateCmd.CommandText = $sqlInsertStmt
          }
      
          $transaction = $connection.BeginTransaction()
      
          $sqlUpdateCmd.Transaction = $transaction
      
          ForEach ($row in $DataSet.Tables[0].Rows)
          {
              $id = $row["Id"]
              $oldValue = $row["IdentityName"]
              $info = "Converting: $oldValue`t DisplayName: " + $row["DisplayName"] + "`t ID=$id"
              Write-Log $info
              
              # Convert to get new value
              $newValue = ConvertOneIdentity $oldValue
              if ($newValue -eq $null)
              {
                  continue
              }
              
              # Run update
              Write-Log ("Get new value ($newValue) for claim: $oldValue")
              
              Try
              {
                  $sqlUpdateCmd.Parameters.Clear()
                  
                  if ($IfRetainOldPrincipals -eq $false)
                  {
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@newIdentityValue", $newValue) 
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@id", $id) 
                  }
                  else
                  {
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@metadataObjectId", $row["MetadataObjectId"])
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@newIdentityValue", $newValue)
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@displayName", $row["DisplayName"])
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@rawSid", $row["RawSid"])
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@rights", $row["Rights"])
                      $null = $sqlUpdateCmd.Parameters.AddWithValue("@sttingId", $row["SettingId"])
                  }
      
                  # Log output
                  Write-Log "Update DB: $($sqlUpdateCmd.CommandText) with new identity: $newValue"
                  if ($pscmdlet.ShouldProcess("Run update DB command")) { 
                      $rc = $sqlUpdateCmd.ExecuteNonQuery()
                      if ($rc -eq 0)
                      {
                          Write-Log "Failed to add/set new value"
                      }
                  }
              }
              Catch [System.Exception]
              {
                  Write-Log ("Failed to add/set new value; error: $($_.Exception.Message)")
              }
              Finally
              {
                  # do nothing
              }
          }
      
          $sqlUpdateCmd.Dispose()
      
          # Commit and Close at last.
          $transaction.Commit()
          $connection.Close() 
      }
      
    4. 複製下列程式碼,並將其貼入 [記事本] 中,然後將檔案儲存為 Migrate-SSSClaims.ps1

      # Description: Migrates the existing Windows claims stored in a Secure Store Service Application database.
       
      function Migrate-SecureStoreClaims
      {
         [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
          param(
              # The site subscription ID of the tenant whose user and group principals in the Managed
              # Metadata service application are to be migrated to the new authentication model.
              [Parameter(Mandatory = $true, ParameterSetName = "tenant", ValueFromPipeline = $true, HelpMessage = "What is the site subscription ID of the tenant you want to migrate?")]
              [ValidateNotNull()]
              [Microsoft.SharePoint.PowerShell.SPSiteSubscriptionPipeBind] $SiteSubscriptionId,
      
              # A site collection whose user and group principals in the Managed Metadata service
              # application are to be migrated to the new authentication model. If the site collection
              # has a site subscription ID, use the -SiteSubscriptionId parameter instead.
              [Parameter(Mandatory = $true, ParameterSetName = "site", ValueFromPipeline = $true, HelpMessage = "Which site collection do you want to migrate?")]
              [ValidateNotNull()]
              [ValidateScript({ $Site.SiteSubscription -eq $null })]
              [Microsoft.SharePoint.PowerShell.SPSitePipeBind] $Site,
      
              # The Managed Metadata web service to be migrated.
              [Parameter(Mandatory = $true)]
              [ValidateNotNull()]
              [Microsoft.SharePoint.Administration.SPIisWebService] $WebService,
      
              # An IMigrateEntityCallback object that performs the entity transformation.
              # [Parameter(Mandatory = $true)]
              # [ValidateNotNull()]
              [Microsoft.SharePoint.Administration.Claims.IMigrateEntityCallback] $Migrator,
      
              #
              [bool] $IfRetainOldPrincipals = $false,
      
              # If set to True, will NOT handle Unpartitioned Secure Store service application
              [bool] $IfSkipUnPartitionedSecureStore
              )
          
          # 
          # Start of functions
          #
          
          # Migrate one claim
          # Return: SPClaim
          function Migrate-Claim([Microsoft.Office.SecureStoreService.Server.SecureStoreServiceClaim]$sssClaim)
          {
              $claimType = $sssClaim.ClaimType
              $claimValue = $sssClaim.ClaimValue
      
              Write-Log "Migrating claim: $claimValue"
      
              $migrationEntity = New-Object Microsoft.SharePoint.Administration.SPMigrationEntity $claimValue
      
              if ($migrationEntity.AuthenticationType -eq [Microsoft.SharePoint.Administration.SPWebApplication+AuthenticationMethod]::Windows)
              {
                  $sourceName = $claimValue
                  $ifSourceReady = $false
                  if ($claimValue -match "^S-\d-\d+-(\d+-){1,14}\d+$")
                  {
                      #SID?
                      if ($claimType.ToLower().Contains("groupsid"))
                      {
                          Write-Log "Need to translate Group SID $claimValue to Security claim..."
                          $spClaim = [Microsoft.SharePoint.Administration.Claims.SPActiveDirectoryClaimProvider]::CreateSecurityGroupClaim($claimValue)
                          $sourceName = $spClaim.ToEncodedString()
                          Write-Log "Translated Security claim is: $sourceName"
                          $ifSourceReady = $true
                      }
                      else
                      {
                          Write-Log "Need to translate SID $claimValue to Windows NT account ..."
                          $objSID = New-Object System.Security.Principal.SecurityIdentifier $claimValue 
                          $objUser = $objSID.Translate( [System.Security.Principal.NTAccount]) 
                          if (!$?)
                          {
                              Write-Log "Cannot convert SID $claimValue"
                              return $null
                          }
                      
                          $sourceName = $objUser.Value
                          Write-Log "Translated to NT account $ntAccount"
                      }
                  }
      
                  if ($ifSourceReady -eq $false)
                  {
                      # Convert "DOMAIN\account" -> "i:0#.w|DOMAIN\account"
                      Write-Log "Need to convert NT account $ntAccount to Windows SAM account ..."
                      $claimsPrincipal = New-SPClaimsPrincipal -IdentityType WindowsSamAccountName -Identity $sourceName
                      $sourceName = $claimsPrincipal.ToEncodedString()
                      if (!$?)
                      {
                          Write-Log "Cannot convert NT account $ntAccount"
                          return $null
                      }
                  }
                  
                  Write-Log "Account has been converted; new value is: $sourceName"
                  $migrationEntity = New-Object Microsoft.SharePoint.Administration.SPMigrationEntity $sourceName
              }
              #>
      
              # Convert
              if ($Migrator -eq $null)
              {
                  Write-Log "No Migrator! Cannot do migration."
                  return $null
              }
      
              $result = $Migrator.ConvertEntity($script:migrationContext, $migrationEntity)
              if ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Success)
              {
                  $newName = $migrationEntity.MigratedName
                  $newValue = [Microsoft.SharePoint.Administration.Claims.SPClaimProviderManager]::Local.DecodeClaim($newName)
      
                  Write-Log "Converted to new claim: $newValue, with name $newName"
                  return $newValue
              }
              else
              {
                  Write-Log ("Cannot convert ( $claimType ) type claim: $claimValue; Converting result is $result")
              }
              
              return $null
          }
      
          #
          # Convert a SecureStoreServiceClaim to SPClaim
          #
          function ConvertSssClaimToSPClaim([Microsoft.Office.SecureStoreService.Server.SecureStoreServiceClaim]$sssClaim)
          {
              return New-Object Microsoft.SharePoint.Administration.Claims.SPClaim $sssClaim.ClaimType, $sssClaim.ClaimValue, "System.String", $sssClaim.ClaimIssuer
          }
      
          #
          # Migrate a group of claims
          # Return: new claims array
          function Migrate-SssClaims([Microsoft.Office.SecureStoreService.Server.SecureStoreServiceClaim[]]$oldClaims)
          {
              if ($oldClaims -eq $null  -or ($oldClaims.Count -le 0))
              {
                  Write-Log("No claim to migrate")
                  return $null
              }
      
              #Write-Log "`n Migrating claims:"
              #Write-Log ($oldClaims)
      
              $newClaims = New-Object System.Collections.ArrayList
              $oldClaims | ForEach {
                  # Write-Log $_
                  $oldClaim = $_
      
                  $newClaim = Migrate-Claim($oldClaim)
                  if ($newClaim)
                  {
                       # add new claim
                      $null = $newClaims.Add($newClaim)
                  }
      
                  if ( ($IfRetainOldPrincipals -eq $true) -or ($newClaim -eq $null))
                  {
                      # remain the old claim when RetainOldPrincipals is set to true or cannot get the new claim
                      $existingClaim = ConvertSssClaimToSPClaim($oldClaim)
                      $null = $newClaims.Add($existingClaim)  
                  }
      
              }
             
              return $newClaims
          }
      
          #
          # Migrate one target application (Secure Store Application)
          # Return: none
          #
          function Migrate-OneApp([Microsoft.Office.SecureStoreService.PowerShellCmdlet.SPSecureStoreApplication]$ssApp)
          {
              <#
                  [System.Collections.Generic.List[Microsoft.Office.SecureStoreService.Server.SecureStoreServiceClaim]]
              #>
      
              Write-Log ("Migrating Target Application: " + $ssApp.TargetApplication.ApplicationId + "`n`n")
      
              $taClaims = $ssApp.TargetApplicationClaims
              $adminClaims = $taClaims.AdministratorClaims
              $groupClaims = $taClaims.GroupClaims
              $redeemClaims = $taClaims.TicketRedeemerClaims
      
              if ($adminClaims)
              {
                  Write-Log "`nMigrating existing Admin Claims..."
                  $adminClaims | ft
                  $newAdminClaims = Migrate-SssClaims $adminClaims
      
                  "`nNew Admin Claims:"
                  $newAdminClaims | ft
              }
              else
              {
                  Write-Log "`nNo admin claims"
              }
      
              if ($groupClaims)
              {
                  Write-Log "`nMigrating existing Group Claims..."
                  $groupClaims | ft
                  
                  $newGroupClaims = Migrate-SssClaims $groupClaims
      
                  "`nNew Group Claims:"
                  $newGroupClaims | ft
              }
              else
              {
                  Write-Log "`nNo group claims"
              }
      
              if ($redeemClaims)
              {
                  Write-Log "`nMigrating existing Ticket Redeemer Claims..."
                  $redeemClaims | ft
                  
                  $newRedeemClaims = Migrate-SssClaims $redeemClaimsClaims
      
                  "`nNew Redeem Claims:"
                  $newRedeemClaims | ft
              }
              else
              {
                  Write-Log "`nNo Ticket Redeemer claims"
              }
              
              # To Update setting
              Write-Log "`nUpdating SPSecureStoreApplication..."
              
              if ($pscmdlet.ShouldProcess("Run command of Set-SPSecureStoreApplication?")) { 
                  Set-SPSecureStoreApplication -Identity $ssApp -Administrator $newAdminClaims -CredentialsOwnerGroup $newGroupClaims -TicketRedeemer $newRedeemClaims
              }
      
              Write-Log ("`nEnd of migration of " + $ssApp.TargetApplication.ApplicationId + "`n")
          }
      
      
          
          #
          # Start of main logic:
          #
          $script:migrationContext = New-Object Microsoft.SharePoint.Administration.Claims.SPClaimMigrationContext($WebService)
      
          # Get Secure Store Applications (target applications)
      
          $serviceContext = Get-SPServiceContext -Site $Site
          $ssApps = Get-SPSecureStoreApplication -ServiceContext $serviceContext -All
      
          $ssApps | % { Migrate-OneApp($_) }
      }
      
    5. 複製下列程式碼,並將其貼入 [記事本] 這類編輯器中,然後將檔案儲存為 Migrate-Helpers.ps1

      # Filename: Migrate-Helpers.ps1
      # Description: Helper functions for migrating Business Connectivity Services and Secure Store Services claims.
      
      function Write-Log($message)
      {
          Write-Host $message
      }
      
      #
      # Get Service Context for the migration
      # The variables are defined in main script
      # 
      function Get-ServiceContext()
      {
          if ($SiteSubscriptionId -ne $null)
          {
              Write-Log "Finding SPServiceContext for SiteSubscription $($SiteSubscriptionId.Id)"
      
              $siteSubscription = Get-SPSiteSubscription -Identity $SiteSubscriptionId
              if (!$?)
              {
                  Write-Log "Cannot find site subscription for: $($SiteSubscriptionId.Id)"
                  return $null
              }
      
              $serviceContext = Get-SPServiceContext -SiteSubscription $siteSubscription
              if (!$?)
              {
                  Write-Log "Cannot get service context for site subscription id $($SiteSubscriptionId.Id)"
                  return $null
              }
      
              return $serviceContext
          }
          elseif ($Site -ne $null)
          {
              if ($Site.SiteSubscription -ne $null)
              {
                  Write-Log "Site collection $($Site.Url) has a site subscription. Run this script with -SiteSubscriptionId $($Site.SiteSubscription.Id)."
                  return $null
              }
      
              Write-Log "Using SPServiceContext for site collection $($Site.Url)"
      
              $serviceContext = Get-SPServiceContext -Site $Site
              return $serviceContext
          }
      
          return $null
      }
      
    6. 複製下列程式碼,並將其貼入 [記事本] 這類編輯器中,然後將檔案儲存為 Migrate-BcsSssClaims.ps1

      #Description: Migrates the existing Windows claims stored in Business Connectivity Services database and Secure Store Service database. 
      <#
      
      .SYNOPSIS
      Performs a migration of all user and group principals in Business Connectivity Services and Secure Store Service Applications to their equivalents under the configured SAML claims authentication provider.
      
      .DESCRIPTION
      This script is provided to allow customers to customize their SAML claims
      migration based on their existing SharePoint 2013 deployment.
      It supports limited customization, primarily through variables that the
      customer can modify to adapt the script to their requirements. These
      variables are in the "User-configurable Parameters" section of this script.
      
      The script iterates through all term store objects (including groups, term
      sets, and terms) in the given Business Connectivity Services and Secure Store Service. If any term store
      object has an associated user or group principal that belongs to the tenant
      identified by the -SiteSubscriptionId parameter, the script converts that
      principal to its equivalent under the SAML claims authentication provider.
      
      If the Managed Metadata service is not part of a tenant environment, as is
      common in the case of on-premises deployments, the script may instead be
      invoked by passing an SPSite object using the -Site parameter.
      
      .EXAMPLE
      Migrate-BcsSssClaims -Site "http://sharepoint.contoso.com"
      
      #>
      
      [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
      param(
          # A site collection with no site subscription. If the site collection has a
          # site subscription, use the -SiteSubscriptionId parameter instead.
          [Parameter(Mandatory = $true, ParameterSetName = "site", ValueFromPipeline = $true, HelpMessage = "Which site collection do you wish to migrate?")]
          [ValidateNotNull()]
          [ValidateScript({ $Site.SiteSubscription -eq $null })]
          [Microsoft.SharePoint.PowerShell.SPSitePipeBind] $Site,
      
          # We need to have a name for SAML identity provider.
          # When it is empty, we assume there's only one provider in the farm.
          #[Parameter(Mandatory = $true, ParameterSetName = "IdentityProviderName", ValueFromPipeline = $false, HelpMessage = "What's the name for SAML Identity Provider?")]
          [System.String] $IdentityProviderName = $null,
          
          # This parameter prevents the original principals from being deleted or overwritten in the
          # database when the migrated principals are written. By default this parameter is false
          # and the original principals are deleted. This parameter is useful for a phased migration
          # where it is necessary to keep the Managed Metadata service application functioning for
          # users connecting from both migrated and non-migrated tenants. Once all tenants
          # have been migrated, the original principals can be deleted by re-running this script
          # with this parameter disabled. Note that for testing migrations, the -WhatIf parameter can
          # also be used to help ensure that no changes are committed to the database.
          # [Parameter(Mandatory = $false, ParameterSetName = "IfRetainOldPrincipals", ValueFromPipeline = $true, HelpMessage = "Do you want to remain the old principals?")]
          [bool] $IfRetainOldPrincipals = $false,  
      
          # If set to True, will NOT handle UnPartitioned Secure Store service application
          [bool] $IfSkipUnPartitionedSecureStore
          
          
      )
      
      #------------------------------------------------------------------------------
      # User-configurable Parameters
      
      # Define any principals that must explicitly NOT be migrated; use commas to
      # separate multiple principals.
      $skipMigrationList = @("some_domain\do_not_migrate_account1", 
                             "some_domain\do_not_migrate_account2",
                             "True",
                             "windows",
                             "002C\test-cr-02" )
      
      $customMapping = @{ "old_domain\old_username1" = "username1@company.com";   # migrates to e.g. i:0e.t|authprovider|username1@company.com
                          "old_domain\old_username2" = "username2@partner.com";   # migrates to e.g. i:0e.t|authprovider|username2@partner.com
                        }
      
      # Loads the SAML claims identity provider. This assumes there is only one provider. 
      $identityProviders = Get-SPTrustedIdentityTokenIssuer
      $identityProvider = $null
      
      if ($identityProviders.Count -eq 1)
      {
          $identityProvider = $identityProviders[0]
      }
      elseif ($identityProviders.Count -gt 1)
      {
          # Loop the providers
          $identityProviders | % {
              if ($_.Name -ne $IdentityProviderName)
              {
                  return; # in fact it works as continue
              }
      
              $identityProvider = $_
          }
      }
      
      if ($identityProvider -eq $null)
      {
          $tmp = "."
          if ($IdentityProviderName)
          {
              $tmp = " named $IdentityProviderName."
          }
      
          Write-Host "Cannot find trusted identity token issuer$tmp"
          Exit
      }
      else
      {
          Write-Host "Find trusted identity token issuer: $($identityProvider.Name)"
      }
      
      
      #------------------------------------------------------------------------------
      # Migration script
      
      # Load the migration script
      . .\Migrate-Helpers.ps1
      . .\Migrate-BcsClaims.ps1
      . .\Migrate-SSSClaims.ps1
      
      $config = New-Object Microsoft.SharePoint.Administration.Claims.SPClaimsMigrationConfiguration
      $skipMigrationList |
          % { $null = $config.SourceSkipMigrationList.Add($_) }
      
      # Create the pre-configured mapping data
      $customMapping.Keys |
          % {
              $value = $customMapping[$_]
              $mappingData = New-Object Microsoft.SharePoint.Administration.SPMigrationMappingData($value, $value)
              $config.MappingData.Add($_, $mappingData)
          }
      
      
      # Create a basic migration configuration
      $trustedConfigArgs = @($identityProvider.Name, $config.FarmIdMappings, $config.SourceSkipMigrationList, $config.MappingData)
      
      $trustedConfig = New-Object Microsoft.SharePoint.Administration.Claims.SPTrustedClaimsMigrationConfiguration $trustedConfigArgs
      
      
      $migrator = New-Object Microsoft.SharePoint.Administration.Claims.SPWindowsToTrustedBackedByActiveDirectoryMigration $trustedConfig
      
      
      #
      # 1. Process Secure Store; TypeName: Secure Store Service Application
      #
      
      $serviceApps = Get-SPServiceApplication | Where-Object { $_.TypeName -match "Secure Store Service Application" }
      Write-Log "Found $($serviceApps.Count) Secure Store Service Application(s) `n`n"
      
      $serviceApps |
          % {
              if ($IfSkipUnPartitionedSecureStore -eq $true -and ($_.Properties["Microsoft.Office.Server.Utilities.SPPartitionOptions"] -eq "UnPartitioned"))
              {
                  Write-Log ("We won't process Un-Partitioned Secure Store Service Application named " + $_.Name)
                  return
              }
      
              Write-Log "`nConnecting to $_ `n"
              $service = $_.Service
      
              # start migration
              Migrate-SecureStoreClaims -WebService $service -Migrator $migrator -Site $Site -IfRetainOldPrincipals $IfRetainOldPrincipals -IfSkipUnPartitionedSecureStore $IfSkipUnPartitionedSecureStore -WhatIf:$WhatIfPreference; 
      
              Write-Log "`nDisconnecting from $_ `n"
          }
      
      
      #
      # 2. Process BCS; TypeName: Business Data Connectivity Service Application
      # We will deal with database directly.
      #
      
      $serviceApps = Get-SPServiceApplication | Where-Object { $_.TypeName -match "Business Data Connectivity Service Application" }
      Write-Log "Found $($serviceApps.Count) Business Connectivity Service Application(s) `n`n"
      
      $serviceApps |
          % {
              Write-Log "`nConnecting to $_ `n"
              $serviceApp = $_
      
              # start migration
              Migrate-BcsClaims -WebServiceApplication $serviceApp -Migrator $migrator -Site $Site -IfRetainOldPrincipals $IfRetainOldPrincipals -WhatIf:$WhatIfPreference; 
      
              Write-Log "`nDisconnecting from $_ `n"
          }
      
      $serviceApps = $null
      $migrator = $null
      $trustedConfig = $null
      $trustedConfigArgs = $null
      $config = $null
      $skipMigrationList = $null
      
    7. 在 [開始] 功能表上,按一下 [所有程式]。

    8. 按一下 [Microsoft SharePoint 2013 產品]。

    9. 按一下 [SharePoint 2013 管理命令介面]。

    10. 在 Windows PowerShell 命令提示字元處,輸入下列命令。

      Migrate-BcsSssClaims -Site "http://sharepoint.mycompany.com" -IdentityProviderName "Your SAML identity provider name"
      

      其中:

      • Site 是 SharePoint Server 的根 URL。

      • IdentityProviderName 是 SAML 身分識別 Token 簽署者的名稱。若要取得所有已知 Token 簽署者清單,請使用 Get-SPTrustedIdentityTokenIssuer Cmdlet。

      • IfRetainOldPrincipals 防止在移轉期間刪除資料庫中的原始 NT 驗證和 Windows 宣告身分識別。

      • IfSkipUnPartitionedSecureStore 不會移轉未分割 Secure Store Service 應用程式的身分識別。

      • WhatIf 是用來測試移轉。不會將變更認可到資料庫。

  4. 執行 IISReset。

本節列出的已知徵狀和解決方法可在您將 Business Connectivity Services 或 Secure Store 身分識別移轉至 SAML 宣告環境時協助您。如果這些建議都無法解決問題,而且需要還原任何已認可到服務資料庫的變更,則可以從服務資料庫的備份中還原服務資料庫 (在移轉步驟的步驟 1 取得)。

以下是一些已知徵狀和解決方法:

  • 移轉指令碼需要 SharePoint 2013 管理命令介面。如果執行指令碼產生 [找不到類型 [Microsoft.SharePoint.*]] 這類錯誤,則必須載入 Windows PowerShell 的嵌入式管理單元。如果是從 SharePoint 2013 管理命令介面 執行指令碼,則已載入這些嵌入式管理單元 (即 Add-PsSnapin Microsoft.SharePoint.PowerShell)。載入嵌入式管理單元之後,請重新執行指令碼。

  • 移轉指令碼會將資訊寫入至記錄檔。記錄檔將協助您了解問題,而且會有四個可能結果:

    • Success:顯示轉換後的值。

    • Failure:許多不同的原因都會傳回此值。其中一個範例是身分識別不屬於移轉中網域時。這是顯示您看到的常見錯誤訊息的範例:Error; cannot convert i:0e.t|regularusers|name@domain

    • AlreadyMigrated:實體已是有效的 SAML 宣告主體名稱,因此不需要進行變更。

    • Skipped:身分識別位於略過的移轉設定清單中,此清單位於移轉指令碼 Migrate-BcsSssClaims.ps1$skipMigrationList 變數中。這是顯示您看到的常見錯誤訊息的範例:Skipped to migrate: i:0#.w|domain\name

  • Conversion failures:無法將資料庫中發現的主體名稱對應至新驗證提供者的對應主體名稱時,會發生這些失敗。此失敗可能會有數個原因 (如下表所示)。

     

    原因 解決方法

    公司或組織不再有該實體 (人員或群組)。

    這是最常見的情況。移轉之後,請在 SharePoint 管理中心網站 中移除 Business Connectivity Services 和 Secure Store 服務的中繼資料權限。

    實體具有身分識別提供者無法從舊名稱對應的新主體名稱。例如,如果人員因為結婚而變更姓名,則會發生此情況。

    移轉之後,請在 SharePoint 管理中心網站 中移除 Business Connectivity Services 和 Secure Store 服務的中繼資料權限,然後重新新增中繼資料權限。

SAML 支援 InfoPath,但需要驗證的 Web Service Connections to SharePoint 服務不予支援。這些不支援的 Web Service Connections 範例包括 User Profile Service Web Service。

本節詳述將 Managed Metadata Service 資料庫從 Windows 宣告 (或 NT 驗證) 身分識別提供者移轉至 SAML 宣告身分識別提供者所需的步驟。移轉是在原地執行。它會修改分類資料庫,因此之後無法在舊環境中使用該資料庫。

Managed Metadata Service 會將已驗證實體的主體名稱儲存至服務資料庫中。這些是用來控制字詞庫管理員、群組管理員、群組參與者、字詞集擁有者和字詞擁有者這類角色的存取。它們也會用來提供字詞集關係人這類實體的變更通知。如果在設定 (或移轉) 具有 SAML 宣告的 Web 應用程式之後佈建 Managed Metadata Service 應用程式,則 Managed Metadata Service 會以正確格式儲存主體名稱,而且所有上述存取檢查和通知都會正確地運作,而不需要進行任何額外設定或移轉步驟。

不過,如果一開始在 Web 應用程式設定成使用不同身分識別提供者 (即 NT 驗證或 Windows 宣告) 時部署 Managed Metadata Service,則會將 Web 應用程式移轉至 SAML 宣告型身分識別提供者。必須執行移轉小節中詳述的移轉程序,服務資料庫才能支援新的提供者。

下表顯示不同身分識別提供者之個別使用者和安全性群組的主體名稱:

 

身分識別提供者

主體名稱 (第一行是範例使用者;第二行則是安全性群組)

NTLM

DOMAIN\username

s-1-5-21-2146773085-903363285-719344707-1309461

Windows 宣告

i:0#.w|DOMAIN\username

c:0+.w|s-1-5-21-2146773085-903363285-719344707-1309461

SAML 宣告 (信任)

i:0e.t|providername|username@domain.com

c:0e.t|providername|s-1-5-21-2146773085-903363285-719344707-1309461

開始移轉之前,請先檢閱下列必要條件的相關資訊:

  • 已安裝具有 2014 年 4 月累積更新 (CU) 的 SharePoint 2013。

  • 將不會移轉服務應用程式管理和SharePoint 管理中心網站,因此會繼續使用現有驗證模型 (例如 NT 驗證或 Windows 宣告)。

  • 管理中心和服務應用程式管理會繼續使用現有驗證模型 (例如 NT 驗證或 Windows 宣告);亦即,將「不」會移轉這些項目。

  • 目標環境設定成使用支援的 SAML 宣告型驗證提供者。

  • 移轉指令碼透過直接對資料庫執行 SQL 查詢來更新 Managed Metadata Service 資料庫。建議在移轉期間停止 Managed Metadata Service。

  • 必須使用可對 Managed Metadata Service 資料庫執行更新指令碼的認證 (即服務應用程式 帳戶) 來執行移轉。

  • 移轉程序使用下列 Windows PowerShell 指令碼:Convert-ManagedMetadataServiceToTrusted.ps1 (稱為 Convert 指令碼)。

若要移轉至 SAML 宣告型驗證,請執行下列動作:

  1. 在SharePoint 管理中心網站中,停止 Managed Metadata Service。

  2. 備份正在移轉之租用戶可用的所有現有使用中 Managed Metadata Service 資料庫。資料庫備份和還原是還原失敗移轉的主要方法。

  3. 選擇性地建立略過檔案。此文字檔包含「不」得移轉的主體名稱。其中,一列會包含一個主體名稱。

    • DOMAIN1\usertobeskipped1

    • DOMAIN1\usertobeskipped2

  4. Convert 指令碼會執行所指定 Web 應用程式的移轉。使用 WhatIf 參數可以執行測試執行。測試執行與完整移轉完全相同,但不會將更新寫入至資料庫。

    使用 Windows PowerShell 執行 Convert 指令碼
    1. 確認符合下列成員資格:

      • SQL Server 執行個體上的 securityadmin 固定伺服器角色。

      • 待更新之所有資料庫上的 db_owner 固定資料庫角色。

      • 您正在執行 Windows PowerShell Cmdlet 之伺服器上的 Administrators 群組。

      可使用 Add-SPShellAdmin Cmdlet 來授與權限的管理員。

      注意事項 附註:
      如果您不具備上述權限,請連絡安裝程式系統管理員或 SQL Server 系統管理員來要求權限。 如需 Windows PowerShell 權限的其他資訊,請參閱<Add-SPShellAdmin>。
    2. 複製下列程式碼,並將其貼入 [記事本] 中,然後將檔案儲存為 Convert-ManagedMetadataServiceToTrusted.ps1

      
      # Description: Migrates principal names in the Managed Metadata Service database
      # for a web application or site collection to SAML-based claim authentication.
      
      <#
      
      .SYNOPSIS
      Converts all principal names in the Managed Metadata Service (MMS) database for a
      web application or site collection from NT Authentication or Windows claims to SAML claims.
      
      .DESCRIPTION
      This script migrates all user or security group principal names stored in the
      MMS database from the NT Authentication / Windows claims identity provider format to the
      SAML claims identity provider format.
      
      If the -WhatIf parameter is specified, no changes are committed to the database.
      
      .EXAMPLE
      Convert-ManagedMetadataServiceToTrusted -WebApplication http://app.contoso.com -TokenIssuer "CorpUsers"
      
      Converts the principal names in the MMS database belonging to the web application to SAML claims.
      
      .EXAMPLE
      Convert-ManagedMetadataServiceToTrusted -Site http://app.contoso.com/sites/teamsite -TokenIssuer "CorpUsers"
          
      Converts the principal names in the MMS database belonging to the site collection's web application to SAML claims.
      
      #>
      
      [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
      param(
          # The web application whose user and group principals are in the Managed Metadata
          # service application that are to be migrated to the SAML claims identity provider.
          [Parameter(Mandatory = $true, ParameterSetName = "webApp", ValueFromPipeline = $true, HelpMessage = "Which web application do you wish to migrate?")]
          [Alias("WebApplication")]
          [ValidateNotNull()]
          [Microsoft.SharePoint.PowerShell.SPWebApplicationPipeBind] $WebApplicationPipeBind,
      
          # A site collection belonging to the tenant whose user and group principals are in the
          # Managed Metadata service application that are to be migrated to the new identity provider.
          [Parameter(Mandatory = $true, ParameterSetName = "site", ValueFromPipeline = $true, HelpMessage = "Which site collection do you wish to migrate?")]
          [Alias("Site")]
          [ValidateNotNull()]
          [Microsoft.SharePoint.PowerShell.SPSitePipeBind] $SitePipeBind,
      
          [Parameter(Mandatory = $true, HelpMessage = "Which identity token issuer should the script use?")]
          [Alias("TokenIssuer", "IdentityTokenIssuer")]
          [ValidateNotNullOrEmpty()]
          [string] $IdentityTokenIssuerName,
      
          # The path to a file containing the principal names that must NOT be migrated.
          # Each row in the file must contain exactly one principal name.
          [Parameter(Mandatory = $false)]
          [ValidateScript({ ![string]::IsNullOrEmpty($_) -and (Test-Path -Path $_ -PathType Leaf) })]
          [string] $SkipFile = $null
      )
      
      #--------------------------------------------------------------------------
      # Script variables
      #
      
      # Context objects needed for migration
      [Microsoft.SharePoint.Administration.SPWebApplication] $script:WebApplication = $null
      [Microsoft.SharePoint.Administration.SPDatabase] $script:Database = $null
      [Microsoft.SharePoint.Administration.Claims.SPClaimMigrationContext] $script:MigrationContext = $null
      [Microsoft.SharePoint.Administration.Claims.IMigrateEntityCallback] $script:Migrator = $null
      
      # Partition ID
      $script:PartitionIds = @()
      $script:PartitionId = $null
      $script:DefaultPartitionId = [GUID]"0C37852B-34D0-418E-91C6-2AC25AF4BE5B"
      
      # Log formatting
      [string] $script:Prefix = [String]::Empty
      [string] $script:PrefixToken = "    "
      [bool] $script:PrefixEnabled = $true
      
      # Tracking
      $script:Counters            = @{}   # Dictionary of counters
      $script:FailureCounts       = @{}   # Map of principal name -> failure count
      $script:Total               = 0
      $script:Current             = 0
      
      # Log files
      $script:MigrationLog        = "ManagedMetadataMigration_{0}.log" -f (Get-Date -f yyyy-MM-dd_hh-mm-ss)
      $script:FailedMigrationsLog = "ManagedMetadataFailedMigrations_{0}.log" -f (Get-Date -f yyyy-MM-dd_hh-mm-ss)
      
      # SQL Regexes
      $script:WindowsClaimsRegex  = "i:0__w|%"
      $script:ClaimsRegex         = "i:0__t|%"
      
      # Query templates
      $script:SelectQueryTemplates = @{
          "ECMPermission" =           "SELECT PrincipalName, PartitionId, GroupId, Rights " +
                                      "FROM [dbo].[ECMPermission] " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND PrincipalName NOT LIKE '{1}' " +
                                      "AND PrincipalName NOT LIKE 'SiteCollection%'";
      
          "ECMTermSetOwner" =         "SELECT DISTINCT Owner " +
                                      "FROM [dbo].[ECMTermSet] " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND Owner NOT LIKE '{1}'" +
                                      "AND Owner <> ''" +
                                      "AND Owner <> ' '" +
                                      "AND Owner <> 'NT AUTHORITY\IUSR'";
      
          "ECMTermSetStakeholder" =   "SELECT DISTINCT Stakeholders " +
                                      "FROM [dbo].[ECMTermSet] " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND Stakeholders <> '' " +
                                      "AND Stakeholders <> ' ' " +
                                      "AND Stakeholders <> 'NT AUTHORITY\IUSR'";
      }
      
      $script:UpdateQueryTemplates = @{
          "ECMPermission" =           "UPDATE [dbo].[ECMPermission] " +
                                      "SET PrincipalName = '{2}' " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND PrincipalName = '{1}'";
                  
          "ECMTermSetOwner" =         "UPDATE [dbo].[ECMTermSet] " +
                                      "SET Owner = '{2}' " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND Owner = '{1}'";
      
          "ECMTermSetStakeholder" =   "UPDATE [dbo].[ECMTermSet] " +
                                      "SET Stakeholders = '{2}' " +
                                      "WHERE PartitionId = '{0}' " +
                                      "AND Stakeholders = '{1}'";
      }
      
      #--------------------------------------------------------------------------
      # Joblet interface
      #
      
      function ShouldExecute()
      {
          $shouldExecute = $false
      
          try
          {
              Write-Log ("Is -WhatIf specified? {0}" -f $WhatIfPreference)
      
              $buildVersion = (Get-SPFarm).BuildVersion
              Write-Log "Detected Microsoft SharePoint version $($buildVersion.ToString())"
              if ($buildVersion.Major -eq 15 -and $buildVersion.Build -lt 4605)
              {
                  Write-LogError "Claims migration for the Managed Metadata service application is only supported for Microsoft SharePoint 2013 with April 2014 CU (build 15.0.4605.*) or later."
                  return $false
              }
      
              # Require farm admin privileges to perform migration.
              # This check only works if the current authentication mode is Windows Authentication (claims or NTLM)
              if (-not [Microsoft.SharePoint.Administration.SPFarm]::Local.CurrentUserIsAdministrator($true))
              {
                  Write-LogError "Managed Metadata service claims migration requires farm administrator privileges. Re-run this script as a farm admin."
                  return $false
              }
      
              $script:PartitionIds = Resolve-PartitionIds
              if (-not $script:PartitionIds -or $script:PartitionIds.Count -eq 0)
              {
                  Write-LogError "Unable to resolve partition ID."
                  return $false
              }
              Write-Log ("Migration scoped to {0} partition IDs" -f $script:PartitionIds.Count)
      
              $script:Migrator = Create-Migrator
              if (-not $script:Migrator)
              {
                  Write-LogError "Unable to create migration object."
                  return $false
              }
      
              $script:MigrationContext = New-Object Microsoft.SharePoint.Administration.Claims.SPClaimMigrationContext($script:WebApplication)
              if (!$? -or $script:MigrationContext -eq $null)
              {
                  Write-LogError ("Unable to create SPClaimMigrationContext for web application '{0}'." -f $script:WebApplication.Name)
                  return $false
              }
      
              $shouldExecute = $true
          }
          catch [Exception]
          {
              Write-LogError ("Exception in ShouldExecute: {0}" -f $_.Exception.Message)
              Write-LogError ("Stack trace: {0}" -f $_.ScriptStackTrace)
          }
      
          Write-Log "ShouldExecute Convert-ManagedMetadataServiceToTrusted? $shouldExecute"
          return $shouldExecute
      }
      
      function Execute([Microsoft.SharePoint.Administration.SPDatabase] $database)
      {
          try
          {
              Write-Log "Executing Convert-ManagedMetadataServiceToTrusted"
              Write-Log "Start Time: $(Get-Date)"
      
              $duration = Measure-Command {
                  Migrate-Database $_.Database
              }
              Write-Log "Finish Time: $(Get-Date)"
              Write-Log ("Duration: {0:n3} seconds" -f $duration.TotalSeconds)
          }
          catch [Exception]
          {
              Write-LogError ("Exception in Execute: {0}" -f $_.Exception.Message)
              Write-LogError ("Stack trace: {0}" -f $_.ScriptStackTrace)
          }
          finally
          {
              Reset-Indent
              Write-Counters
              Write-FailedMigrationSummary
              Write-Log "Finished executing Convert-ManagedMetadataServiceToTrusted"
          }
      }
      
      #--------------------------------------------------------------------------
      # Logging helpers
      #
      
      function Write-Log([string]$message, [switch]$Indent, [switch]$UnIndent, [switch]$NoNewline, [switch]$NoPrefix, [switch]$NoOutput)
      {
          if ($Indent) { Indent-Log }
          $script:PrefixEnabled = -not $NoPrefix
          if ($PrefixEnabled) { $log = $Prefix + $message } else { $log = $message }
          Write-Host -NoNewline:$NoNewLine $log
          if (-not $NoOutput) { Write-Output $log | Out-File -Append -WhatIf:$false -FilePath $script:MigrationLog }
          $script:PrefixEnabled = -not $NoNewline
          if (Test-Path function:\Write-GridLog) { Write-GridLog -Status OK $log }
          if ($UnIndent) { UnIndent-Log }
      }
      
      function Write-LogError([string]$message, [switch]$Indent, [switch]$UnIndent)
      {
          if ($Indent) { Indent-Log }
          $log = $Prefix + "ERROR: " + $message
          Write-Error $log
          Write-Output $log | Out-File -Append -WhatIf:$false -FilePath $script:MigrationLog
          if ($UnIndent) { UnIndent-Log }
      }
      
      function Write-FailedMigration([string]$principalName)
      {
          # Write the failed migration details to the log file
          $principalName |
              Out-File -Append -WhatIf:$false -FilePath $script:FailedMigrationsLog
      }
      
      function Indent-Log()
      {
          $script:Prefix += $PrefixToken
      }
      
      function UnIndent-Log()
      {
          $len = $Prefix.Length - $PrefixToken.Length
          if ($len -gt 0) {
              $script:Prefix = $Prefix.Substring(0, $len)
          } else {
              $script:Prefix = [string]::Empty
          }
      }
      
      function Reset-Indent()
      {
          $script:Prefix = [String]::Empty
      }
      
      function Increment([string]$Name, [int]$Count = 1)
      {
          $script:Counters.$Name += $Count
      }
      
      function Increment-Counter($Name, [int]$Count = 1)
      {
          if ($Name.GetType().Name -eq "SPMigrateEntityCallbackResult") { Increment -Name "Convert_$Name" -Count $Count }
              else { Increment -Name $Name -Count $Count }
      }
      
      function Increment-Counters($Names, [int]$Count = 1)
      {
          $Names | % { Increment-Counter -Name $_ -Count $Count }
      }
      
      function Reset-Counter($name)
      {
          $script:Counters.$name = 0
      }
      
      function Write-Counters()
      {
          Write-Log "----------------------------------------------------"
          Write-Log "Counters:"
          $script:Counters.Keys |
              Sort-Object |
                  % { Write-Log -Indent -UnIndent ("{0,-34}: {1,7}" -f $_, $script:Counters.$_) }
          Write-Log "----------------------------------------------------"
      }
      
      function Write-FailedMigrationSummary()
      {
          if ((-not $script:FailureCounts) -or ($script:FailureCounts.Count -eq 0)) { Write-LogError "No failed conversions"; return }
          if ($script:FailureCounts.Count -ge 10) { $n = 10 } else { $n = $script:FailureCounts.Count }
          Write-Log "Top $n Failed Conversions:"
          $sortedFailures = $script:FailureCounts.GetEnumerator() |
              sort -Descending -Property Value |
              select -First 10 |
              % { Write-Log -Indent -UnIndent $_.Key }
          Write-Log "----------------------------------------------------"
      }
      
      #--------------------------------------------------------------------------
      # Script Helpers
      #
      
      function Load-SkipFile([System.Collections.Generic.HashSet[string]] $SkipList, [string] $Filename)
      {
          if (-not $Filename) { Write-Log "No skip file specified (OK)"; return }
          Write-Log -NoNewline "Loading list of principal names to skip from $Filename ..."
      
          # Each line contains a single principal name, that is,
          #   OLDDOMAIN1\skipuser1
          #   OLDDOMAIN1\skipuser2
          #   ...
          Get-Content -ReadCount 1000 -Path $Filename |
              % { $_ } |
              % {
                  $added = $SkipList.Add($_)
                  if (-not $added) { Write-Log ("Duplicate entry found in skip list: {0} (ignoring)" -f $_) }
              }
      
          Write-Log " Done"
          Write-Log ("Loaded {0} principal names that will not be migrated" -f $SkipList.Count)
      }
      
      function Create-Migrator()
      {
          $config = New-Object Microsoft.SharePoint.Administration.Claims.SPClaimsMigrationConfiguration
      
          Load-SkipFile -SkipList $config.SourceSkipMigrationList -Filename $SkipFile
      
          # Find the claims identity provider
          $identityProvider = Get-SPTrustedIdentityTokenIssuer $IdentityTokenIssuerName
          if (-not $identityProvider)
          {
              Write-LogError ("Could not find identity token issuer with name '{0}'" -f $IdentityTokenIssuerName)
              return $null
          }
      
          # Create a claims migration configuration
          $configArgs = @($identityProvider.Name, $config.FarmIdMappings, $config.SourceSkipMigrationList, $config.MappingData)
          $trustedConfig = New-Object Microsoft.SharePoint.Administration.Claims.SPTrustedClaimsMigrationConfiguration $configArgs
      
          # Construct the SAML claims migration object
          $migrator = New-Object Microsoft.SharePoint.Administration.Claims.SPWindowsToTrustedBackedByActiveDirectoryMigration $trustedConfig
          $canMigrate = $migrator.IntializaForWebApplication($script:WebApplication)
          if (-not $canMigrate)
          {
              Write-LogError "SPWebApplication '{0}' is not in a good state for migration." -f $script:WebApplication.Name
              return $null
          }
      
          $script:Config = $config
      
          return $migrator
      }
      
      function Resolve-PartitionIds()
      {
          $partitionIds = @()
          if ($WebApplicationPipeBind)
          {
              $script:WebApplication = $WebApplicationPipeBind.Read()
              $script:WebApplication.SiteSubscriptions |
                  % {
                      $identifier = $_.Id
                      $partitionIds += $identifier.Id
                  }
              
              if ((Get-SPFarm).BuildVersion.Major -eq 15) { $partitionIds += $script:DefaultPartitionId }
          }
          elseif ($SitePipeBind)
          {
              $site = $SitePipeBind.Read()
              if ($site)
              {
                  $script:WebApplication = $site.WebApplication
                  if ($site.SiteSubscription) { $partitionIds += $site.SiteSubscription.Id }
                  else { $partitionIds += $script:DefaultPartitionId }
              }
          }
      
          return $partitionIds
      }
          
      function Execute-SelectQuery([string] $SelectQuery, [string] $Extractor)
      {
          $results = @()
      
          try
          {
              $connection = New-Object System.Data.SqlClient.SqlConnection $script:Database.DatabaseConnectionString
              $connection.Open()
      
              $command = $connection.CreateCommand()
              $command.CommandText = $SelectQuery
      
              $dataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter($command)
              $dataSet = New-Object System.Data.DataSet
              $rowsFetched = $dataAdapter.Fill($dataSet)
      
              Write-Log "Fetched $rowsFetched rows from database"
      
              $dataSet.Tables[0].Rows |
                  % { $results += Invoke-Expression $Extractor }
          }
          finally
          {
              $connection.Close()
          }
      
          return $results
      }
      
      function Execute-UpdateQuery([string] $UpdateQuery)
      {
          $rowsAffected = 0
      
          try
          {
              $connection = New-Object System.Data.SqlClient.SqlConnection $script:Database.DatabaseConnectionString
              $connection.Open()
      
              $command = $connection.CreateCommand()
              $command.CommandText = $UpdateQuery
              $rowsAffected = $command.ExecuteNonQuery()
          }
          finally
          {
              $connection.Close()
          }
      
          return $rowsAffected
      }
      
      function Write-MigratedName($Names, [string]$QueryTemplate)
      {
          Write-Log ("Writing {0} migrated value(s) to database" -f $Names.Count)
          $totalRowsAffected = 0
      
          $Names.Keys |
              % {
                  $query = Invoke-Expression $QueryTemplate
                  Write-Log "Query: $query"
                  if ($PSCmdlet.ShouldProcess("Commit changes to database (Y/N)?"))
                  {
                      $rowsAffected = Execute-UpdateQuery -Database $database -UpdateQuery $query
                      Increment-Counter "Updates_Executed"
                      Increment-Counter -Name "RowsUpdated" -Count $rowsAffected
                      $totalRowsAffected += $rowsAffected
                  }
                  Increment-Counter "Updates"
              }
      
          Write-Log ("Successfully written {0} migrated value(s) to database; {1} row(s) affected." -f $Names.Length, $totalRowsAffected)
      }
      
      
      #--------------------------------------------------------------------------
      # Migration Methods
      #
      
      function Migrate-Database([Microsoft.SharePoint.Administration.SPDatabase] $database)
      {
          Write-Log ("Processing database {0}" -f $database.Name)
      
          if ($database.Type -ne "Microsoft.SharePoint.Taxonomy.MetadataWebServiceDatabase")
          {
              Write-LogError ("Database is not a Managed Metadata Service Database. {0}" -f $database.Type)
              return
          }
      
          $script:Database = $database
      
          $script:PartitionIds | % {
              $script:PartitionId = $_
              Write-Log ("Migrating partition ID {0}" -f $script:PartitionId)
              Migrate-Permissions
              Migrate-TermSetOwners
              Migrate-TermSetStakeholders
              Write-Log ("Finished migrating partition ID {0}" -f $script:PartitionId)
              $script:PartitionId = $null
          }
      
          $script:Database = $null
      }
      
      function Migrate-Permissions()
      {
          # Get all the principal names from the ECMPermission table
          $query = ($script:SelectQueryTemplates.ECMPermission -f $script:PartitionId, $script:ClaimsRegex)
          $principalNames = Execute-SelectQuery -SelectQuery $query -Extractor "(`$_.PrincipalName)"
      
          Write-Log ("Found {0} principal names in the ECMPermission table" -f $principalNames.Length)
      
          # Convert the principal names; result is a map of old -> new
          $newPrincipalNames = Convert-PrincipalNames $principalNames
      
          # Update the ECMPermissions table with the new principal names
          Write-MigratedName -Names $newPrincipalNames -QueryTemplate "(`$script:UpdateQueryTemplates.ECMPermission -f '$script:PartitionId', `$_, `$Names[`$_])"
      }
      
      function Migrate-TermSetOwners()
      {
          # Get all the owner names from the ECMTermSet table
          $query = ($script:SelectQueryTemplates.ECMTermSetOwner -f $script:PartitionId, $script:ClaimsRegex)
          $owners = Execute-SelectQuery -SelectQuery $query -Extractor "(`$_.Owner)"
      
          Write-Log ("Found {0} term set owners" -f $owners.Length)
      
          # Convert the owner names
          $newOwners = Convert-PrincipalNames $owners
      
          Write-MigratedName -Names $newOwners -QueryTemplate "(`$script:UpdateQueryTemplates.ECMTermSetOwner -f '$script:PartitionId', `$_, `$Names[`$_])"
      }
      
      function Migrate-TermSetStakeholders([GUID]$partitionId)
      {
          # Get all the stakeholder names from the ECMTermSet table
          $query = ($script:SelectQueryTemplates.ECMTermSetStakeholder -f $script:PartitionId)
          $stakeholders = Execute-SelectQuery -SelectQuery $query -Extractor "(`$_.Stakeholders)"
      
          Write-Log ("Found {0} term sets with non-empty stakeholders" -f $stakeholders.Length)
          $script:Total = $stakeholders.Length
          $script:Current = 0
          $updatedStakeholders = @{}
      
          $stakeholders |
              ? { -not [string]::IsNullOrWhiteSpace($_) } |
              % {
                  $script:Current++
                  $names = @()
                  $oldNames = $_
                  Write-Log -Indent "Stakeholders: $oldNames"
                  $oldNames.Split(";", [System.StringSplitOptions]::RemoveEmptyEntries) |
                      % {
                          # Convert the stakeholder name
                          $name = Convert-PrincipalName $_
                          if ($name) { $names += $name }
                          else { $names += $_ }
                      }
                  $newNames = $names -join ";"
                  if ($newNames.Length -gt 1000)
                  {
                      Write-LogError ("Stakeholders string length is {0}. Maximum supported length is 1000." -f $newNames.Length, $termSetId)
                      $newNames = $oldNames
                  }
      
                  Write-Log -Indent -UnIndent -NoNewLine "=> Migrated Stakeholders: "
                  if ($newNames -eq $oldNames) { Write-Log -UnIndent -NoPrefix "(no change)" }
                  else
                  {
                      Write-Log -UnIndent -NoPrefix $newNames
                      $updatedStakeholders.Add($oldNames, $newNames)
                  }
              }
      
          Write-MigratedName -Names $updatedStakeholders -QueryTemplate "(`$script:UpdateQueryTemplates.ECMTermSetStakeholder -f '$script:PartitionId', `$_, `$Names[`$_])"
      }
      
      #--------------------------------------------------------------------------
      # Identity API
      #
      
      function Convert-PrincipalNames($oldPrincipalNames = @())
      {
          Write-Log ("Converting {0} principal names (total)" -f $oldPrincipalNames.Length)
          $script:Total = $oldPrincipalNames.Length
          $script:Current = 0
          $results = @{}
      
          $oldPrincipalNames |
              ? { -not [string]::IsNullOrEmpty($_) } |    # Filter null/empty
              Sort-Object -Unique |                       # Case-insensitive
              % {
                  $script:Current++
                  $newPrincipalName = Convert-PrincipalName $_
                  if ($newPrincipalName -and
                      $newPrincipalName -ne $_) { $results.Add($_, $newPrincipalName) }
              }
          Write-Log ("Found conversions for {0} of {1} principal names" -f $results.Count, $oldPrincipalNames.Length)
          return $results
      }
      
      function Convert-PrincipalName([string] $principalName)
      {
          # Skip names that start with "SiteCollectionId:"
          if ($principalName -match "^SiteCollectionId:") { Increment-Counter "SiteCollection"; return $principalName }
      
          $migrationEntity = Get-ClaimsMigrationEntity($principalName)
      
          try
          {
              # Convert "i:0#.w|DOMAIN\account" -> "i:0e.t|authprovider|email@company.com"
              $result = $Migrator.ConvertEntity($script:MigrationContext, $migrationEntity)
              Increment-Counter $result
          }
          catch [Exception]
          {
              $result = [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Failure
              Increment-Counter "Convert_Exception"
          }
      
          if ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Success)
          {
              $newPrincipalName = $migrationEntity.MigratedName
              $resultString = $newPrincipalName
          }
          else
          {
              $newPrincipalName = $principalName
              $resultString = "[$result]"
      
              if ($result -eq [Microsoft.SharePoint.Administration.SPMigrateEntityCallbackResult]::Failure)
              {
                  $script:FailureCounts[$principalName.ToLowerInvariant()]++
                  Write-FailedMigration $principalName
                  if ($DeleteUnmigratedPrincipals)
                  {
                      $newPrincipalName = $null
                      $resultString += " (delete)"
                  }
              }
          }
      
          $inputString = $principalName
          $log = "{2,11}: {0,-56} -> {1}" -f $inputString, $resultString, ("{0}/{1}" -f $script:Current, $script:Total)
          Write-Log $log
              
          return $newPrincipalName
      }
      
      function Get-ClaimsMigrationEntity([string] $principalName)
      {
          $migrationEntity = New-Object Microsoft.SharePoint.Administration.SPMigrationEntity($principalName)
      
          if ($migrationEntity.AuthenticationType -eq [Microsoft.SharePoint.Administration.SPWebApplication+AuthenticationMethod]::Windows)
          {
              # Convert "DOMAIN\account" -> "i:0#.w|DOMAIN\account"
              $claimsPrincipalName = Convert-WindowsPrincipalNameToClaimsPrincipalName($principalName)
              $migrationEntity = New-Object Microsoft.SharePoint.Administration.SPMigrationEntity($claimsPrincipalName)
          }
      
          return $migrationEntity
      }
      
      function Is-SecurityGroup([string] $principalName)
      {
          # Match "s-1-5-..." (i.e. only return true for SIDs)
          return ($principalName -match "^s-[-\d]+$")
      }
      
      function Convert-WindowsPrincipalNameToClaimsPrincipalName([string] $principalName)
      {
          $claim = $null
          if (Is-SecurityGroup $principalName)
          {
              # Convert "s-1-5-..." -> "c:0+.w|s-1-5-..."
              $claim = [Microsoft.SharePoint.Administration.Claims.SPActiveDirectoryClaimProvider]::CreateSecurityGroupClaim($principalName)
          }
          else
          {
              # Convert "DOMAIN\account" -> "i:0#.w|DOMAIN\account"
              $claim = New-SPClaimsPrincipal -IdentityType WindowsSamAccountName -Identity $principalName
          }
          return $claim.ToEncodedString()
      }
      
      #--------------------------------------------------------------------------
      # Script Execution
      #
      
      $serviceApps = Get-SPServiceApplication | Where-Object { $_.TypeName -match "Managed Metadata Service" }
      Write-Output "Found $($serviceApps.Count) Managed Metadata Service Application(s)"
      
      $serviceApps |
          % {
              $service = $_.Service
              if (ShouldExecute)
              {
                  Write-Log ("Migrating {0} ({1})" -f $service, $service.Id)
                  Execute $_.Database
                  Write-Log ("Migration of {0} ({1}) completed" -f $service, $service.Id)
              }
          } 
      
      
    3. 在 [開始] 功能表上,按一下 [所有程式]。

    4. 按一下 [Microsoft SharePoint 2013 產品]。

    5. 按一下 [SharePoint 2013 管理命令介面]。

    6. 在 Windows PowerShell 命令提示字元處,輸入下列命令。

      ./Convert-ManagedMetadataServiceToTrusted.ps1 -WebApplication <web-application-url> -TokenIssuer <token-issuer-name> -SkipFile <path-to-skip-file> -WhatIf
      

      其中:

      • WebApplication 是要移轉其在服務應用程式資料庫中資料的 Web 應用程式 URL。

      • TokenIssuer 是 SAML 身分識別 Token 簽署者的名稱。若要取得所有已知 Token 簽署者清單,請使用 Get-SPTrustedIdentityTokenIssuer Cmdlet。

      • SkipFile 是上面步驟 3 所建立略過檔案的路徑。此參數為選用。

      指令碼顯示所執行作業的詳細輸出。如果稍後需要進行檢閱,則也會將此輸出寫入至檔案 ManagedMetadataMigration_<timestamp>.log。這是指令碼輸出的開頭:

      偵測到 Microsoft SharePoint 15.0.4605.1002 版

      範圍設為 1 分割區識別碼的移轉

      未指定略過檔案 (OK)

      未指定對應檔案 (OK)

      ShouldExecute Convert-TenantManagedMetadataServiceToTrusted? True

      移轉 MetadataWebService (0fa9be29-e920-4b16-953e-4211ca6bc673)

      執行 Convert-TenantManagedMetadataServiceToTrusted

      開始時間: 04/15/2014 17:06:58

      處理資料庫 MMS

      移轉分割區識別碼 0c37852b-34d0-418e-91c6-2ac25af4be5b

      已從資料庫提取 18 個資料列

      在 ECMPermission 表格中找到 18 個主體名稱

      轉換 18 個主體名稱 (總計)

  5. 移轉每個主體名稱可能會導致下列其中一種可能的結果 (共四種):

    • Success:顯示轉換後的值。

    • Failure:許多不同的原因都會傳回此值。請參閱疑難排解來診斷這些失敗。

    • AlreadyMigrated:實體已是有效的 SAML 宣告主體名稱,因此不需要進行變更。

    • Skipped:特定實體已在略過清單中 (請參閱本節開頭的步驟 3)。

    結果會顯示在每個使用者轉換的旁邊 (如下表所示):

     

    1/18:

    i:0e.t|corpusers|testpa01@domain.com

    [AlreadyMigrated]

    2/18:

    i:0#.w|002c\testpa02

    [Failure]

    3/18:

    i:0#.w|test\testaccount

    [Skipped]

    4/18:

    i:0#.w|002c\testpa04

    i:0e.t|corpusers|testpa04@domain.com

  6. 此指令碼也會產生失敗記錄 (ManagedMetadataFailedMigrations_<timestamp>.log),內含傳回 Failure 結果的所有主體名稱。此記錄的一列會包含一個主體名稱:

     

    002c\testpa02

    002c\olduser2

    otherdomain\otheruser

  7. 在指令碼的結尾,會產生簡短的報告:

     

    計數器:

    Convert_AlreadyMigrated

    1

    Convert_Failure

    4

    Convert_Skipped

    1

    Convert_Success

    13

    更新

    13

    前 4 個失敗實體

    otherdomain\otheruser

    otherdomain\unknownuser1

    otherdomain\unknownuser2

    otherdomain\unknownuser3

    此報告中的重要數字是 Convert_SuccessConvert_Failure 計數。針對每個導致失敗的轉換,ManagedMetadataFailedMigrations_*.log 檔案中會有一個項目 (請參閱步驟 6)。

  8. 如果報告失敗轉換,請分析失敗記錄 (請參閱步驟 6) 來決定哪些使用者失敗。確信移轉正確的使用者時,請重新執行沒有 WhatIf 參數的指令碼。這會將變更認可到資料庫。

  9. 重新啟動步驟 1 中所停用的 Managed Metadata Service。

  10. 對任何前端網頁伺服器執行 Iisreset,以清除 Managed Metadata Service 的權限快取中的任何項目。

本節列出的已知徵狀和解決方法可在您將 Managed Metadata Service 身分識別移轉至 SAML 宣告環境時協助您。如果這些建議都無法解決問題,而且需要還原任何已認可到服務資料庫的變更,則可以從服務資料庫的備份中還原服務資料庫 (在先前移轉步驟的步驟 2 取得)。

以下是一些已知徵狀和解決方法:

  • 移轉指令碼需要 SharePoint 2013 管理命令介面。如果執行指令碼產生 [找不到類型 [Microsoft.SharePoint.*]] 這類錯誤,則必須載入 Windows PowerShell 的嵌入式管理單元。如果是從 SharePoint 2013 管理命令介面 執行指令碼,則已載入這些嵌入式管理單元 (即 Add-PsSnapin Microsoft.SharePoint.PowerShell)。載入嵌入式管理單元之後,請重新執行指令碼。

  • Conversion failures:無法將資料庫中發現的主體名稱對應至新驗證提供者的對應主體名稱時,會發生這些失敗。此失敗可能會有數個原因 (如下表所示)。

     

    原因

    解決方法

    公司或組織不再有該實體 (人員或群組)。

    這是最常見的情況。移轉之後,請移至字詞庫管理員,並編輯群組或字詞集以指派正確的權限。

    否則,如果可以放心地略過實體,請將主體名稱新增至略過檔案 (請參閱<移轉>小節中的步驟 3)。

    實體具有身分識別提供者無法從舊名稱對應的新主體名稱。例如,如果人員因為結婚而變更姓名,則會發生此情況。

    移轉之後,請移至字詞庫管理員,並編輯群組或字詞集以指派正確的權限。

    實體是系統或伺服器陣列管理員帳戶。

    這些帳戶必須保留在 NT 驗證或 Windows 宣告下。請將這些項目新增至略過檔案 (請參閱<移轉>小節中的步驟 3)。

    無法因任何其他原因而移轉實體。

    如果無法略過實體 (請參閱<移轉>小節中的步驟 3),請在移轉之後移至字詞庫管理員,並編輯群組或字詞集以指派正確的權限。

    注意事項 附註:
    這也可以透過使用分類公用物件模型的 Windows PowerShell 來完成。
  • 如果指令碼在一般執行期間因資料庫逾時或鎖定而失敗,則只要使用完全相同的參數重新執行指令碼即可 (假設其他保留鎖定的查詢已完成)。您可以完全重新輸入指令碼。它會重新處理每個項目,但只會對尚未移轉的實體執行更新。

  • 字詞庫管理員使用使用者介面中的人員選擇器控制項來轉譯與編輯使用者和群組。在部分情況下,如果啟用多個驗證提供者,則在儲存對字詞庫、群組、字詞集或字詞內容進行的變更時,控制項可能無法正確地解析使用者名稱。這是已知問題,而因應措施是移除所有實體,並將它們重新新增至人員選擇器控制項,然後進行儲存。也會儲存頁面上所有其他內容的變更。

如果有任何未解決的移轉問題,請一定要儲存 Convert 指令碼的輸出記錄和失敗記錄。也請擷取輸出記錄中所報告時間戳記之間的這段時段的統一登入服務 (ULS) 記錄。需要有這些記錄,才能分析問題。如需如何設定 ULS 記錄的其他資訊,請參閱在 SharePoint 2013 中檢視診斷記錄

https://technet.microsoft.com/zh-tw/library/dn720355.aspx
顯示: