Created
November 5, 2015 17:27
-
-
Save MaxMelcher/6d7ae728073d3657f362 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.TERMS | |
Refer to Microsoft Connect Terms of use http://connect.microsoft.com/terms.aspx#O | |
.SYNOPSIS | |
This script onboards your SharePoint Online (SPO) tenant into the cloud hybrid search SharePoint Server 2013 Preview/SharePoint 2016 Preview | |
.PARAMETER PortalUrl | |
SharePoint Online portal URL, for example 'https://contoso.sharepoint.com'. | |
.PARAMETER HybridSsaId | |
Name or id (Guid) of the cloud hybrid search application, created with the CreateCloudSSA script. | |
.PARAMETER Credential | |
Logon credential for tenant admin. Will be prompted if not specified. | |
#> | |
Param( | |
[Parameter(Mandatory=$true, HelpMessage="SharePoint Online portal URL, for example 'https://contoso.sharepoint.com'.")] | |
[ValidateNotNullOrEmpty()] | |
[string] $PortalUrl, | |
[Parameter(Mandatory=$false, HelpMessage="Name or id (Guid) of the cloud hybrid search application, created with the CreateCloudSSA script.")] | |
[ValidateNotNullOrEmpty()] | |
[string] $HybridSsaId, | |
[Parameter(Mandatory=$false, HelpMessage="Logon credential for tenant admin. Will be prompted if not specified.")] | |
[PSCredential] $Credential | |
) | |
if ($ACS_APPPRINCIPALID -eq $null) { | |
New-Variable -Option Constant -Name ACS_APPPRINCIPALID -Value '00000001-0000-0000-c000-000000000000' | |
New-Variable -Option Constant -Name ACS_HOST -Value "accounts.accesscontrol.windows.net" | |
New-Variable -Option Constant -Name PROVISIONINGAPI_WEBSERVICEURL -Value "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" | |
New-Variable -Option Constant -Name SCS_AUTHORITIES -Value @( | |
"*.search.msit.us.trafficmanager.net", | |
"*.search.production.us.trafficmanager.net", | |
"*.search.production.emea.trafficmanager.net", | |
"*.search.production.apac.trafficmanager.net" | |
) | |
} | |
New-Variable -Option Constant -Name SCS_APPPRINCIPALID -Value '8f0dc9ad-0d19-4fec-a421-6d0279080014' | |
New-Variable -Option Constant -Name SCS_APPPRINCIPALDISPLAYNAME -Value 'Search Content Service' | |
New-Variable -Option Constant -Name SP_APPPRINCIPALID -Value '00000003-0000-0ff1-ce00-000000000000' | |
New-Variable -Option Constant -Name SPO_MANAGEMENT_APPPROXY_NAME -Value 'SPO App Management Proxy' | |
New-Variable -Option Constant -Name ACS_APPPROXY_NAME -Value 'ACS Proxy' | |
New-Variable -Option Constant -Name ACS_STS_NAME -Value 'ACS-STS' | |
New-Variable -Option Constant -Name AAD_METADATAEP_FSTRING -Value 'https://{0}/{1}/metadata/json/1' | |
$SP_VERSION = "15" | |
$regKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office Server\15.0\Search" -ErrorAction SilentlyContinue | |
if ($regKey -eq $null) { | |
$regKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office Server\16.0\Search" -ErrorAction SilentlyContinue | |
if ($regKey -eq $null) { | |
throw "Unable to detect SharePoint installation." | |
} | |
$SP_VERSION = "16" | |
} | |
Write-Host "Configuring for SharePoint version $SP_VERSION." | |
function Configure-LocalSharePointFarm | |
{ | |
Param( | |
[Parameter(Mandatory=$true)][string] $Realm | |
) | |
# Set up to authenticate as AAD realm | |
Set-SPAuthenticationRealm -Realm $Realm | |
$localSecurityConfig = Get-SPSecurityTokenServiceConfig | |
$localSecurityConfig.NameIdentifier = '{0}@{1}' -f $SP_APPPRINCIPALID,$Realm | |
$localSecurityConfig.Update() | |
$acsMetadataEndpoint = $AAD_METADATAEP_FSTRING -f $ACS_HOST,$Realm | |
Write-Host "ACS metatada endpoint: $acsMetadataEndpoint" | |
# ACS Proxy | |
$acsProxy = Get-SPServiceApplicationProxy | ? {$_.TypeName -eq "Azure Access Control Service Application Proxy" -and $_.MetadataEndpointUri -eq [System.Uri] $acsMetadataEndpoint} | |
if ($acsProxy -eq $null) { | |
Write-Host "Setting up ACS proxy..." -Foreground Yellow | |
$acsProxy = Get-SPServiceApplicationProxy | ? {$_.DisplayName -eq $ACS_APPPROXY_NAME} | |
if ($acsProxy -ne $null) { | |
throw "There is already a service application proxy registered with name '$($acsProxy.DisplayName)'. Remove manually and retry." | |
} | |
$acsProxy = New-SPAzureAccessControlServiceApplicationProxy -Name $ACS_APPPROXY_NAME -MetadataServiceEndpointUri $acsMetadataEndpoint -DefaultProxyGroup | |
} elseif ($acsProxy.Count > 1) { | |
throw "Found multiple existing ACS proxies for this metadata endpoint." | |
} else { | |
Write-Host "Found existing ACS proxy '$($acsProxy.DisplayName)'." -Foreground Green | |
} | |
# The proxy must be in default group and set as default for authentication to work | |
if (((Get-SPServiceApplicationProxyGroup -Default).DefaultProxies | select Id).Id -notcontains $acsProxy.Id) { | |
throw "ACS proxy '$($acsProxy.DisplayName)' is not set as the default. Configure manually through Service Application Associations admin UI and retry." | |
} | |
# Register ACS token issuer | |
$acsTokenIssuer = Get-SPTrustedSecurityTokenIssuer | ? {$_.MetadataEndPoint -eq [System.Uri] $acsMetadataEndpoint} | |
if ($acsTokenIssuer -eq $null) { | |
Write-Host "Registering ACS as trusted token issuer..." -Foreground Yellow | |
$acsTokenIssuer = Get-SPTrustedSecurityTokenIssuer | ? {$_.DisplayName -eq $ACS_STS_NAME} | |
if ($acsTokenIssuer -ne $null) { | |
throw "There is already a token issuer registered with name '$($acsTokenIssuer.DisplayName)'. Remove manually and retry." | |
} | |
try { | |
$acsTokenIssuer = New-SPTrustedSecurityTokenIssuer -Name $ACS_STS_NAME -IsTrustBroker -MetadataEndPoint $acsMetadataEndpoint -ErrorAction Stop | |
} catch [System.ArgumentException] { | |
Write-Warning "$($_)" | |
} | |
} elseif ($acsTokenIssuer.Count > 1) { | |
throw "Found multiple existing token issuers for this metadata endpoint." | |
} else { | |
if ($acsTokenIssuer.IsSelfIssuer -eq $true) { | |
Write-Warning "Existing trusted token issuer '$($acsTokenIssuer.DisplayName)' is configured as SelfIssuer." | |
} else { | |
Write-Host "Found existing token issuer '$($acsTokenIssuer.DisplayName)'." -Foreground Green | |
} | |
} | |
# SPO proxy | |
$spoProxy = Get-SPServiceApplicationProxy | ? {$_.TypeName -eq "SharePoint Online Application Principal Management Service Application Proxy" -and $_.OnlineTenantUri -eq [System.Uri] $PortalUrl} | |
if ($spoProxy -eq $null) { | |
Write-Host "Setting up SPO Proxy..." -Foreground Yellow | |
$spoProxy = Get-SPServiceApplicationProxy | ? {$_.DisplayName -eq $SPO_MANAGEMENT_APPPROXY_NAME} | |
if ($spoProxy -ne $null) { | |
throw "There is already a service application proxy registered with name '$($spoProxy.DisplayName)'. Remove manually and retry." | |
} | |
$spoProxy = New-SPOnlineApplicationPrincipalManagementServiceApplicationProxy -Name $SPO_MANAGEMENT_APPPROXY_NAME -OnlineTenantUri $PortalUrl -DefaultProxyGroup | |
} elseif ($spoProxy.Count > 1) { | |
throw "Found multiple existing SPO proxies for this tenant URI." | |
} else { | |
Write-Host "Found existing SPO proxy '$($spoProxy.DisplayName)'." -Foreground Green | |
} | |
# The proxy should be in default group and set to default | |
if (((Get-SPServiceApplicationProxyGroup -Default).DefaultProxies | select Id).Id -notcontains $spoProxy.Id) { | |
throw "SPO proxy '$($spoProxy.DisplayName)' is not set as the default. Configure manually through Service Application Associations admin UI and retry." | |
} | |
return $localSecurityConfig.LocalLoginProvider.SigningCertificate | |
} | |
function Upload-SigningCredentialToSharePointPrincipal | |
{ | |
Param( | |
[Parameter(Mandatory=$true)][System.Security.Cryptography.X509Certificates.X509Certificate2] $Cert | |
) | |
$exported = $Cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | |
$certValue = [System.Convert]::ToBase64String($exported) | |
$principal = Get-MsolServicePrincipal -AppPrincipalId $SP_APPPRINCIPALID | |
$keys = Get-MsolServicePrincipalCredential -ObjectId $principal.ObjectId -ReturnKeyValues $true | ? Value -eq $certValue | |
if ($keys -eq $null) { | |
New-MsolServicePrincipalCredential -AppPrincipalId $SP_APPPRINCIPALID -Type Asymmetric -Value $certValue -Usage Verify | |
} else { | |
Write-Host "Signing credential already exists in SharePoint principal." | |
} | |
} | |
function Add-ScsServicePrincipal | |
{ | |
$spns = $SCS_AUTHORITIES | foreach { "$SCS_APPPRINCIPALID/$_" } | |
$principal = Get-MsolServicePrincipal -AppPrincipalId $SCS_APPPRINCIPALID -ErrorAction SilentlyContinue | |
if ($principal -eq $null) { | |
Write-Host "Creating new service principal for $SCS_APPPRINCIPALDISPLAYNAME with the following SPNs:" | |
$spns | foreach { Write-Host $_ } | |
$scspn = New-MsolServicePrincipal -AppPrincipalId $SCS_APPPRINCIPALID -DisplayName $SCS_APPPRINCIPALDISPLAYNAME -ServicePrincipalNames $spns | |
} else { | |
$update = $false | |
$spns | foreach { | |
if ($principal.ServicePrincipalNames -notcontains $_) { | |
$principal.ServicePrincipalNames.Add($_) | |
Write-Host "Adding new SPN to existing service principal: $_." | |
$update = $true | |
} | |
} | |
if ($update -eq $true) { | |
Set-MsolServicePrincipal -AppPrincipalId $principal.AppPrincipalId -ServicePrincipalNames $principal.ServicePrincipalNames | |
} else { | |
Write-Host "Service Principal already registered, containing the correct SPNs." | |
} | |
} | |
} | |
function Prepare-Environment | |
{ | |
$MSOIdCRLRegKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\MSOIdentityCRL" -ErrorAction SilentlyContinue | |
if ($MSOIdCRLRegKey -eq $null) { | |
Write-Host "Office Single Sign On Assistant required, see http://www.microsoft.com/en-us/download/details.aspx?id=3926." -Foreground Red | |
} else { | |
Write-Host "Found Office Single Sign On Assistant!" -Foreground Green | |
} | |
$MSOLPSRegKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\MSOnlinePowershell" -ErrorAction SilentlyContinue | |
if ($MSOLPSRegKey -eq $null) { | |
Write-Host "AAD PowerShell required, see http://go.microsoft.com/fwlink/p/?linkid=236297." -Foreground Red | |
} else { | |
Write-Host "Found AAD PowerShell!" -Foreground Green | |
} | |
if ($MSOIdCRLRegKey -eq $null -or $MSOLPSRegKey -eq $null) { | |
throw "Manual installation of prerequisites required." | |
} | |
Write-Host "Configuring Azure AD settings..." -Foreground Yellow | |
$regkey = "HKLM:\SOFTWARE\Microsoft\MSOnlinePowerShell\Path" | |
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\MSOIdentityCRL" -Name "ServiceEnvironment" -Value "Production" | |
Set-ItemProperty -Path $regkey -Name "WebServiceUrl" -Value $PROVISIONINGAPI_WEBSERVICEURL | |
Set-ItemProperty -Path $regkey -Name "FederationProviderIdentifier" -Value "microsoftonline.com" | |
Write-Host "Restarting MSO IDCRL Service..." -Foreground Yellow | |
# Service takes time to get provisioned, retry restart. | |
for ($i = 1; $i -le 10; $i++) { | |
try { | |
Stop-Service -Name msoidsvc -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | |
$svc = Get-Service msoidsvc | |
$svc.WaitForStatus("Stopped") | |
Start-Service -Name msoidsvc | |
} catch { | |
Write-Host "Failed to start msoidsvc service, retrying..." | |
Start-Sleep -seconds 2 | |
continue | |
} | |
Write-Host "Service Restarted!" -Foreground Green | |
break | |
} | |
} | |
function Get-HybridSsa | |
{ | |
$ssa = $null | |
if (-not $HybridSsaId) { | |
$ssa = Get-SPEnterpriseSearchServiceApplication | |
if ($ssa.Count -ne 1) { | |
Write-Host "There are multiple Search Service Applications." -ForegroundColor Yellow | |
$ssa = Get-SPEnterpriseSearchServiceApplication | ? { $_.CloudIndex -eq $true } | |
if ($ssa.Count -ne 1) | |
{ | |
Write-Host -ForegroundColor red "Found more than one Search Service Application with CloudIndex - please restart this script with one of the following SSA ID as parameter:" | |
foreach($s in $ssa) | |
{ | |
Write-Host $s.Name ":" $s.Id | |
} | |
} | |
else | |
{ | |
Write-Host -ForegroundColor Green "Found: " $ssa.Name | |
} | |
} | |
} else { | |
$ssa = Get-SPEnterpriseSearchServiceApplication -Identity $HybridSsaId | |
} | |
if ($ssa -eq $null) { | |
throw "Hybrid SSA not found." | |
} | |
# Make sure SSA is created with CreateCloudSSA.ps1 | |
if ($ssa.CloudIndex -ne $true) { | |
throw "The provided SSA is not set up for hybrid, please create a cloud hybrid SSA before proceeding with onboarding." | |
} | |
Write-Host "Using SSA with id $($ssa.Id)." | |
$ssa.SetProperty("IsHybrid", 1) | |
$ssa.Update() | |
return $ssa | |
} | |
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")).Location | |
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")).Location | |
Add-Type -Path ([System.Reflection.Assembly]::Load("Microsoft.SharePoint.Client.Search,Version=$SP_VERSION.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")).Location | |
$code = @" | |
using System; | |
using System.Net; | |
using System.Security; | |
using Microsoft.SharePoint; | |
using Microsoft.SharePoint.Administration; | |
using Microsoft.SharePoint.Client; | |
using Microsoft.SharePoint.IdentityModel; | |
using Microsoft.SharePoint.IdentityModel.OAuth2; | |
static public class ClientContextHelper | |
{ | |
public static ClientContext GetAppClientContext(string siteUrl) | |
{ | |
SPServiceContext serviceContext = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default); | |
using (SPServiceContextScope serviceContextScope = new SPServiceContextScope(serviceContext)) | |
{ | |
ClientContext clientContext = new ClientContext(siteUrl); | |
ICredentials credentials = null; | |
clientContext.ExecutingWebRequest += (sndr, request) => | |
{ | |
request.WebRequestExecutor.RequestHeaders.Add(HttpRequestHeader.Authorization, "Bearer"); | |
request.WebRequestExecutor.WebRequest.PreAuthenticate = true; | |
}; | |
// Run elevated to get app credentials | |
SPSecurity.RunWithElevatedPrivileges(delegate() | |
{ | |
credentials = SPOAuth2BearerCredentials.Create(); | |
}); | |
clientContext.Credentials = credentials; | |
return clientContext; | |
} | |
} | |
} | |
"@ | |
$assemblies = @( | |
"System.Core.dll", | |
"System.Web.dll", | |
"Microsoft.SharePoint, Version=$SP_VERSION.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", | |
"Microsoft.SharePoint.Client, Version=$SP_VERSION.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", | |
"Microsoft.SharePoint.Client.Runtime, Version=$SP_VERSION.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" | |
) | |
Add-Type -TypeDefinition $code -ReferencedAssemblies $assemblies | |
Add-PSSnapin Microsoft.SharePoint.PowerShell | |
try | |
{ | |
Write-Host "Accessing Hybrid SSA..." -Foreground Yellow | |
$ssa = Get-HybridSsa | |
Write-Host "Preparing environment..." -Foreground Yellow | |
Prepare-Environment | |
Import-Module MSOnline | |
Import-Module MSOnlineExtended -force | |
Write-Host "Connecting to O365..." -Foreground Yellow | |
if ($Credential -eq $null) { | |
$Credential = Get-Credential -Message "Tenant Admin credential" | |
} | |
Connect-MsolService -Credential $Credential -ErrorAction Stop | |
$tenantInfo = Get-MsolCompanyInformation | |
$AADRealm = $tenantInfo.ObjectId.Guid | |
Write-Host "AAD tenant realm is $AADRealm." | |
Write-Host "Configuring on-prem SharePoint farm..." -Foreground Yellow | |
$signingCert = Configure-LocalSharePointFarm -Realm $AADRealm | |
Write-Host "Adding local signing credential to SharePoint principal..." -Foreground Yellow | |
Upload-SigningCredentialToSharePointPrincipal -Cert $signingCert | |
Write-Host "Configuring service principal for the cloud search service..." -Foreground Yellow | |
Add-ScsServicePrincipal | |
Write-Host "Connecting to content farm in SPO..." -foreground Yellow | |
$cctx = [ClientContextHelper]::GetAppClientContext($PortalUrl) | |
$pushTenantManager = new-object Microsoft.SharePoint.Client.Search.ContentPush.PushTenantManager $cctx | |
# Retry up to 4 minutes, mitigate 401 Unauthorized from CSOM | |
Write-Host "Preparing tenant for hybrid..." -foreground Yellow | |
for ($i = 1; $i -le 12; $i++) { | |
try { | |
$pushTenantManager.PreparePushTenant() | |
$cctx.ExecuteQuery() | |
Write-Host "PreparePushTenant was successfully invoked!" -Foreground Green | |
break | |
} catch { | |
if ($i -ge 12) { | |
throw "Failed to call PreparePushTenant, error was $($_.Exception.Message)" | |
} | |
Write-Warning "$($_.Exception.Message), retrying..." | |
Start-Sleep -seconds 20 | |
} | |
} | |
Write-Host "Getting service info..." -foreground Yellow | |
$info = $pushTenantManager.GetPushServiceInfo() | |
$info.Retrieve("EndpointAddress") | |
$info.Retrieve("TenantId") | |
$info.Retrieve("AuthenticationRealm") | |
$info.Retrieve("ValidContentEncryptionCertificates") | |
$cctx.ExecuteQuery() | |
Write-Host "Registered hybrid configuration:" | |
$info | select TenantId,AuthenticationRealm,EndpointAddress | format-list | |
if ([string]::IsNullOrEmpty($info.EndpointAddress)) { | |
throw "No indexing service endpoint found!" | |
} | |
$ServiceEndpointUri = [System.Uri] $info.EndpointAddress | |
if ($info.ValidContentEncryptionCertificates -eq $null) { | |
Write-Warning "No valid encryption certificate found, encrypted crawl might not work." | |
} | |
if ($AADRealm -ne $info.AuthenticationRealm) { | |
throw "Unexpected mismatch between realm ids read from Get-MsolCompanyInformation ($AADRealm) and GetPushServiceInfo ($($info.AuthenticationRealm))." | |
} | |
Write-Host "Configuring Hybrid SSA..." -foreground Yellow | |
$ssa.SetProperty("PushServiceLocation", ($ServiceEndpointUri.AbsoluteUri).TrimEnd('/')) | |
$ssa.SetProperty("CertServerURL", $PortalUrl) | |
$ssa.SetProperty("HybridTenantID", $info.TenantId) | |
$ssa.SetProperty("AuthRealm", $info.AuthenticationRealm) | |
$ssa.Update() | |
Write-Host "Restarting search service..." -foreground Yellow | |
if ($SP_VERSION -eq "15") { | |
Restart-Service OSearch15 | |
} else { | |
Restart-Service OSearch16 | |
} | |
Write-Host "All done!" -foreground Green | |
} | |
catch | |
{ | |
Write-Error -ErrorRecord $_ | |
Write-Host "It is safe to re-run onboarding if you believe this error is transient." -Foreground Yellow | |
return | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment