Export (0) Print
Expand All

Implications of Claims Mode Authentication on service applications

SharePoint 2013
 

Applies to: SharePoint Server 2013

Topic Last Modified: 2014-05-29

Summary: Learn how to migrate Windows claims authentication to SAML-based authentication on service applications in SharePoint 2013.

This article describes how to migrate Windows claims to SAML-based authentication on service applications.

In this article:

This section describes the steps to migrate a Windows claim identity stored in Microsoft Business Connectivity Services and Secure Store Service databases to SAML-based identities.

Before you start the migration, review the following information about prerequisites:

  • SharePoint 2013 with the April 2014 Cumulative Update (CU) is installed.

  • SharePoint Server 2013.

  • Business Connectivity Services (BCS) and Secure Store exist and have NT authentication or Windows claims identities.

  • The target environment is configured to use a supported SAML claims-based identity provider.

  • The migration will be performed by using the farm administrator credential.

  • The Migrate-BcsSssClaims.ps1 script migrates existing Windows claims identities that are stored in a Business Connectivity Services database and Secure Store database.

To perform the migration of Windows claims-based identities that are stored in a Business Connectivity Services database and Secure Store database, do the following:

  1. Back up the existing BCS and Secure Store service database by using Microsoft SQL Server Management Studio. Database backup and restore is the primary means to revert a failed migration.

  2. Create a migration skip list and custom map:

    1. Open the Migrate-BcsSssClaims.ps1 script file by using Microsoft PowerShell ISE or an editor like Notepad.

    2. Find the $skipMigrationList variable. It contains the principals that must explicitly not be migrated. Use commas to separate multiple principals. Here's an example: $skipMigrationList = @("some_domain\do_not_migrate_account1", "some_domain\do_not_migrate_account2").

    3. Find the $customMapping variable. The custom mapping explicitly converts the identities to the SAML code you specified. The following example migrates a user identity named old_domain\old_username1 to the SAML identity username1@company.com and old_domain\old_username2 to username2@partner.com:

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

  3. The Migrate-BcsSssClaims.ps1 script performs a migration of all user and group principals in Business Connectivity Services and Secure Store applications to the SAML claims-based authentication provider.

    Run the Migrate-BcsSssClaims script by using Windows PowerShell
    1. Verify that you have the following memberships:

      • The securityadmin fixed server role on the SQL Server instance.

      • The db_owner fixed database role on all databases that are to be updated.

      • The Administrators group on the server on which you are running the Windows PowerShell cmdlets.

      • An administrator can use the Add-SPShellAdmin cmdlet to grant permissions.

      NoteNote:
      If you do not have permissions, contact your Setup administrator or SQL Server administrator to request permissions. For additional information about Windows PowerShell permissions, see Add-SPShellAdmin.
    2. NoteNote:
      The following script files (that is, Migrate-BcsClaims.ps1, Migrate-SSSClaims.ps1, and Migrate-Helpers.ps1) need to be copied and saved in the same location as the Migrate-BcsSssClaims.ps1 file. They are used when the Migrate-BcsSssClaims.ps1 file is run.
    3. Copy the following code, paste into an editor like Notepad, and then save the file as 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. Copy the following code, paste it into Notepad, and then save the file as 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. Copy the following code, paste into an editor like Notepad, and then save the file as 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. Copy the following code, paste it into Notepad, and then save the file as 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. On the Start menu, click All Programs.

    8. Click Microsoft SharePoint 2013 Products.

    9. Click SharePoint 2013 Management Shell.

    10. At the Windows PowerShell command prompt, type the following command.

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

      Where:

      • Site is the root URL of the SharePoint Server.

      • IdentityProviderName is the name of a SAML identity token issuer. To get a list of all known token issuers, use the Get-SPTrustedIdentityTokenIssuer cmdlet.

      • IfRetainOldPrincipals prevents the original NT authentication and Windows claims identities from being deleted in the database during the migration.

      • IfSkipUnPartitionedSecureStore does not perform migration for identities of an unpartitioned Secure Store Service application.

      • WhatIf is used to test the migration. No changes are committed to the database.

  4. Run IISReset.

This section lists the known symptoms and resolutions that can help when you migrate a Business Connectivity Services or Secure Store identity to a SAML claims environment. If none of these suggestions resolve the problem and any changes already committed to the service database need to be reverted, the service database can be restored from its backup (taken in step 1 of the migration steps).

Here are some known symptoms and resolutions:

  • The migration scripts require a SharePoint 2013 Management Shell. If running the script generates errors like Unable to find type [Microsoft.SharePoint.*], chances are that the snap-ins for Windows PowerShell have to be loaded. These snap-ins (that is, Add-PsSnapin Microsoft.SharePoint.PowerShell) are already loaded if the script is run from SharePoint 2013 Management Shell. After the snap-ins are loaded, rerun the script.

  • The migration script will write information to a log file. The log file will help you understand the issue and has four possible outcomes:

    • Success: The converted value is displayed.

    • Failure: This value can be returned for many different reasons. One example is if the identity doesn't belong to the migrating domain. This is an example of a common error message you be see displayed: Error; cannot convert i:0e.t|regularusers|name@domain.

    • AlreadyMigrated: The entity is already a valid SAML claims principal name and requires no change.

    • Skipped: The identity is in the skipped list of the migration configuration, which is located in the $skipMigrationList variable of the migration script Migrate-BcsSssClaims.ps1. This is an example of a common error message you be see displayed: Skipped to migrate: i:0#.w|domain\name.

  • Conversion failures: These occur when a principal name encountered in the database cannot be mapped to the corresponding principal name under the new authentication provider. There can be several reasons for this, as illustrated in the following table.

     

    Reason Resolution

    The entity (person or group) is no longer with the company or organization.

    This is the most common case. After migration, in the SharePoint Central Administration website, remove the metadata permission from Business Connectivity Services and Secure Store services.

    The entity has a new principal name that cannot be mapped from the old name by the identity provider. This can arise, for example, if a person gets married and his or her name changes.

    After migration, in the SharePoint Central Administration website, remove the metadata permission from the Business Connectivity Services and Secure Store services, and then add the metadata permission again.

InfoPath is supported in SAML, but Web Service Connections to SharePoint services that require authentication are not supported. Examples of these unsupported Web Service Connections include the User Profile Service Web Service.

This section details the steps needed to migrate the Managed Metadata service database from a Windows claims (or NT authentication) identity provider to a SAML claims identity provider. The migration is executed in-place. It modifies the Taxonomy database, and the database cannot be used in the old environment afterward.

The Managed Metadata service stores principal names for authenticated entities in the service database. These are used to control access for roles like Term Store Administrators, Group Managers, Group Contributors, Term Set Owners, and Term Owners. They are also used to provide change notifications to entities like Term Set Stakeholders. If a Managed Metadata service application is provisioned after configuring (or migrating) the web application with SAML claims, the Managed Metadata Service will store principal names in the correct format, and all the above access checks and notifications will work correctly without any extra configuration or migration steps.

However, if a Managed Metadata service is initially deployed when the web application is configured to use a different identity provider (that is, NT authentication or Windows claims), the web application will be migrated to a SAML claims-based identity provider. The migration process detailed in the Migration section must be executed for the service database to support the new provider.

The following table shows what principal names for individual users and security groups look like for different identity providers:

 

Identity provider

Principal name (first line is a sample user; second line is the security group)

NTLM

DOMAIN\username

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

Windows claims

i:0#.w|DOMAIN\username

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

SAML claims (Trusted)

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

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

Before you start the migration, review the following information about prerequisites:

  • SharePoint 2013 with the April 2014 Cumulative Update (CU) is installed.

  • The Service Application Management and the SharePoint Central Administration website will not be migrated and will continue to the existing authentication model (likely NT Authentication or Windows claims).

  • Central Administration and Service Application Management will continue to the existing authentication model (likely NT Authentication or Windows claims)—that is, these will not be migrated.

  • The target environment is configured to use a supported SAML claims-based authentication provider.

  • The migration script updates the Managed Metadata service database by directly running SQL queries against the database. It is recommended that the Managed Metadata service be stopped during the migration.

  • The migration must be performed by using credentials that can execute update scripts against the Managed Metadata service database (that is, the Service Application account).

  • The migration process uses the following Windows PowerShell script: Convert-ManagedMetadataServiceToTrusted.ps1 (known as the Convert script).

To perform the migration to SAML claims-based authentication, do the following:

  1. In the SharePoint Central Administration website, stop the Managed Metadata service.

  2. Back up all existing active Managed Metadata service databases available to the tenant that is being migrated. Database backup and restore is the primary means to revert a failed migration.

  3. Optionally create a skip file. This is a text file that contains principal names that must not be migrated. It contains one principal name per row.

    • DOMAIN1\usertobeskipped1

    • DOMAIN1\usertobeskipped2

  4. The Convert script performs the migration for a specified web application. Use the WhatIf parameter to perform a test run. A test run is exactly like a full migration except it does not write updates to the database.

    Run the Convert script by using Windows PowerShell
    1. Verify that you have the following memberships:

      • The securityadmin fixed server role on the SQL Server instance.

      • The db_owner fixed database role on all databases that are to be updated.

      • The Administrators group on the server on which you are running the Windows PowerShell cmdlets.

      An administrator can use the Add-SPShellAdmin cmdlet to grant permissions.

      NoteNote:
      If you do not have permissions, contact your Setup administrator or SQL Server administrator to request permissions. For additional information about Windows PowerShell permissions, see Add-SPShellAdmin.
    2. Copy the following code, paste it into Notepad, and then save the file as 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. On the Start menu, click All Programs.

    4. Click Microsoft SharePoint 2013 Products.

    5. Click SharePoint 2013 Management Shell.

    6. At the Windows PowerShell command prompt, type the following command.

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

      Where:

      • WebApplication is the URL of the web application whose data in the service application database is to be migrated.

      • TokenIssuer is the name of a SAML identity token issuer. To get a list of all known token issuers, use the Get-SPTrustedIdentityTokenIssuer cmdlet.

      • SkipFile is the path to the skip file created in step 3 above. This parameter is optional.

      The script displays detailed output of everything it is doing. This output is also written to the file ManagedMetadataMigration_<timestamp>.log in case it needs to be reviewed later. This is what the beginning of the script output looks like:

      Detected Microsoft SharePoint version 15.0.4605.1002

      Migration scoped to 1 partition IDs

      No skip file specified (OK)

      No mapping file specified (OK)

      ShouldExecute Convert-TenantManagedMetadataServiceToTrusted? True

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

      Executing Convert-TenantManagedMetadataServiceToTrusted

      Start Time: 04/15/2014 17:06:58

      Processing database MMS

      Migrating partition ID 0c37852b-34d0-418e-91c6-2ac25af4be5b

      Fetched 18 rows from database

      Found 18 principal names in the ECMPermission table

      Converting 18 principal names (total)

  5. The migration of each principal name can result in one of four possible outcomes:

    • Success: The converted value is displayed.

    • Failure: This value can be returned for many different reasons. See Troubleshooting to diagnose these.

    • AlreadyMigrated: The entity is already a valid SAML claims principal name and requires no change.

    • Skipped: The particular entity was in the skip list (see step 3 at the beginning of this section).

    The outcomes are displayed next to each user conversion, as illustrated in the following table:

     

    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. The script also produces the failure log (ManagedMetadataFailedMigrations_<timestamp>.log) containing all the principal names that returned a Failure result. This log contains one principal name per row:

     

    002c\testpa02

    002c\olduser2

    otherdomain\otheruser

  7. At the end of the script, a short report is generated:

     

    Counters:

    Value

    Convert_AlreadyMigrated

    1

    Convert_Failure

    4

    Convert_Skipped

    1

    Convert_Success

    13 

    Updates

    13

      

      

    Top 4 Failed Entities

    otherdomain\otheruser

    N/A

    otherdomain\unknownuser1

    N/A

    otherdomain\unknownuser2

    N/A

    otherdomain\unknownuser3

    N/A

    The key numbers in this report are the Convert_Success and Convert_Failure counts. For each conversion that results in a failure, there will be an entry in the ManagedMetadataFailedMigrations_*.log file (see step 6.)

  8. If failed conversions are reported, analyze the failure log (see step 6) to determine which users failed. When you are confident that the correct users are being migrated, re-run the script without the WhatIf parameter. This will commit the changes to the database.

  9. Restart the Managed Metadata service disabled in step 1.

  10. Run Iisreset on any front-end web servers to clear any entries in the Permissions Cache in the Managed Metadata Service.

This section will list known symptoms and resolutions that can help when you migrate a managed metadata service to a SAML claims environment. If none of these suggestions resolve the problem, and any changes already committed to the service database need to be reverted, the service database can be restored from its backup (taken in step 2 of the previous migration steps).

Here are some known symptoms and resolutions:

  • The migration scripts require SharePoint 2013 Management Shell. If running the script generates errors like Unable to find type [Microsoft.SharePoint.*], chances are that the snap-ins for Windows PowerShell have to be loaded. These snap-ins (that is, Add-PsSnapin Microsoft.SharePoint.PowerShell) are already loaded if the script is run from SharePoint 2013 Management Shell. Once the snap-ins are loaded, re-run the script.

  • Conversion failures- These occur when a principal name encountered in the database cannot be mapped to the corresponding principal name under the new authentication provider. There can be several reasons for this, as illustrated in the following table:

     

    Reason

    Resolution

    The entity (person or group) is no longer with the company or organization.

    This is the most common case. After migration, go to the term store manager and edit the group or term set to assign the correct permissions.

    Otherwise, if the entity can safely be skipped, add the principal name to the skip file (see step 3 in the Migration section).

    The entity has a new principal name that cannot be mapped from the old name by the identity provider. This can arise, for example, if a person gets married and his or her name changes.

    After migration, go to the term store manager and edit the group or term set to assign the correct permissions.

    The entity is a system or farm administrator account.

    These accounts must remain under NT Authentication or Windows claims. Add these to the skip file (see step 3 in the Migration section).

    The entity cannot be migrated for any other reason. 

    If the entity cannot be skipped (see step 3 in the Migration section), go to the term store manager after migration and edit the group or term set to assign the correct permissions.

    NoteNote:
    This can also be done by using Windows PowerShell using the Taxonomy Public Object Model.
  • If the script failed during ordinary execution due to a database timeout or lock, simply re-run the script with exactly the same parameters (assuming the other query holding the lock has completed). The script is fully re-entrant. It will reprocess every entry, but it will execute updates only on entities that have not already been migrated.

  • The term store manager uses people picker controls in the user interface to render and edit users and groups. In some cases when multiple authentication providers are enabled, the control might not correctly resolve user names when saving changes made to the term store, group, term set, or term properties. This is a known issue, and the workaround is to remove and re-add all the entities to the people picker control again and then save. Changes to all other properties on the page will then also be saved.

If there are any unresolved problems with the migration, be sure to save the output log and failure log from the Convert script. Also extract the Unified Logging System (ULS) logs for the time period between the time stamps reported in the output log. These logs will be necessary to analyze the problem. For additional information about how to configure ULS logging, see View diagnostic logs in SharePoint 2013.

Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft