Friday, January 9, 2015

Workaround to make SharePoint online one-way outbound hybrid search to display online result even SharePoint online is not using UPN as login



When we implement SharePoint online one-way outbound hybrid search, we were experiencing users could not get SharePoint online result. The reason is we are NOT using UPN for SharePoint online users. The SharePoint online users are using different logins like userid@domain.company.com and on-premises UPN is not synced to Azure AD.

After working with Microsoft support engineers, we understood that SharePoint hybrid search claims & authentication process is based on set of SharePoint Online User profile attributes sent over to match on-premise UPN. If user’s on-premises UPN value matches any of the following SharePoint online user attributes from UPS, the user will be considered as authenticated and search result will be send to on-premises.

  • The Windows Security Identifier (SID)                                               
  • The Active Directory Domain Services (AD DS) user principal name (UPN
  • The Simple Mail Transfer Protocol (SMTP) address                                               
  • The Session Initiation Protocol (SIP) address                                                    

As a result, if companies’ SharePoint online logins are different from on-premises user UPNs,  the workaround to make one-way outbound hybrid search work is to map on-premises UPN value to one of the above online user profile attributes. Please refer Microsoft reference for all the UPS attributes.

The least impact attribute we could see is the SIP value.  Here is the script to update ALL SharePoint online user SIP in UPS with on-premises UPN value. I'm using web service since CSOM API does not seem to be able to update online SIP value. The key function is UserProfileService.ModifyUserPropertyByAccountName that will update the UPS attribute value.


$a = Get-Date
Write-Host "Time before:" $a -ForegroundColor Green

# Add snapin if necessary
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null) {
        Write-Host   $(Get-Date -format "dd_MM_yyyy_HH_mm_ss") '- Loading SharePoint Powershell Snapin'
        Write-Host
        Add-PSSnapin "Microsoft.SharePoint.Powershell"
}


# Output file in csv format in current folder
$reportFile = $myinvocation.mycommand.path.Replace($MyInvocation.MyCommand.name,"") + 'upsmapsip_' + $(Get-Date -format "dd_MM_yyyy_HH_mm_ss") + '.csv';
# Add header to the output file
$line = "O365UserLogin" + “,” + "Email"  + “,” + "SIP" + “,” + "UPN"
Add-Content $reportFile $line

#Configure Site URL and User
$cred = Get-Credential;

# Tod: Please note we need all users to be added to this site!!!
$siteUrl = "https://mycompony.sharepoint.com/";
$adminUrl = "https://mycompony-admin.sharepoint.com";

#Add references to SharePoint client assemblies and authenticate to Office 365 site - required for CSOM
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.UserProfiles.dll"

$Creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($cred.UserName,$cred.Password)

#Bind to Site Collection
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$Context.Credentials = $Creds

#Identify users in the O365.User group
$root = [ADSI]''
$searcher = New-Object System.DirectoryServices.DirectorySearcher($root)

# Please change user group for your query
$searcher.filter = "(&(objectcategory=user)(memberof=CN=O365.Users,OU=Distribution Lists,OU=Exchange,DC=na,DC=qualcomm,DC=com))"
$users = $searcher.findall()

$adminCookie = $Creds.GetAuthenticationCookie($adminUrl)

# Get the authentication cookie by passing the url of the web service
$siteCookie = $Creds.GetAuthenticationCookie($siteUrl);

# Create a CookieContainer to authenticate against the web service
$authContainer = New-Object System.Net.CookieContainer;

# Put the authenticationCookie string in the container
$authContainer.SetCookies($adminUrl, $authCookie);

# Concatenate the URL for Web Service / REST API
$url = $adminUrl  + "/_vti_bin/userprofileservice.asmx";


# Create the O365 REST service           
$UserProfileWS = $null;          

try{           
 $UserProfileWS=New-WebServiceProxy -Uri $url -Namespace 'SPOUserProfileService';   
    $UserProfileWS.UseDefaultCredentials = $false;


    $UserProfileWS.CookieContainer = New-Object System.Net.CookieContainer;
    $UserProfileWS.CookieContainer.SetCookies($adminUrl, $adminCookie);  

    # Assign previously created auth container to web service
    #$UserProfileWS.CookieContainer = $authContainer;   

    $targetSite = New-Object Uri($adminUrl);

    #$UserProfileWS.CookieContainer = New-Object System.Net.CookieContainer;

    Write-Host $authCookie;
    $secondPartOfCookie = $adminCookie.TrimStart("SPOIDCRL=".ToCharArray());

    $cookie = New-Object System.Net.Cookie;
    $cookie.Name = "FedAuth";
    $cookie.Value = $secondPartOfCookie;
   

    $UserProfileWS.CookieContainer.Add($cookie);

}           
catch{            
    Write-Error $_ -ErrorAction:'SilentlyContinue';            
}

# Create People Manager object to retrieve O365 profile data
$PeopleManager = New-Object Microsoft.SharePoint.Client.UserProfiles.PeopleManager($Context)

for ($i=0; $i -le $users.Count – 1; $i++) 
{

    $user = $users.Item($i)
    $email = $user.Properties["mail"]
    $login = "i:0#.f|membership|" + $user.Properties["mail"]


    $UserProfile = $PeopleManager.GetPropertiesFor($login)

    try
    {
        Write-Host "User LoginName:" $User.LoginName -ForegroundColor Green
        Write-Host "User Email:" $User.Email -ForegroundColor Green

        # Query AD to get user UPN value through email

        $searcher.filter = "(&(objectClass=user)(mail= $email))"
        $aduser = $searcher.findall()
        $UPNValue = $aduser[0].Properties["userprincipalname"]
      
        Write-Host "UPN Value:" $UPNValue.Item(0) -ForegroundColor Green

        # Update the SIP of O365 UPS for this user

        $userProperty = $UserProfileWS.GetUserPropertyByAccountName($login, 'SPS-SipAddress')
        $currentsid = $userProperty[0].Values[0].Value;
        $userProperty[0].Values[0].Value = $UPNValue.Item(0);
        $userProperty[0].IsValueChanged = $true;
        $UserProfileWS.ModifyUserPropertyByAccountName($login, $userProperty)


        $line = $login + “,” + '"' + $email  + '"'  + “,” + $currentsid + “,” + '"' + $UPNValue.Item(0) + '"'
        Add-Content $reportFile $line

    }
    catch
    {            
        Write-Error $_ -ErrorAction:'SilentlyContinue';            
    }   

}

$b = Get-Date
Write-Host "Time after:" $b -ForegroundColor Green

After you run this script, you will have UPS SIP value updated to on-premises UPN. Users will be able to get SharePoint online search result from on-premises search center. I have a different script to update only users in specif AD group.

In the longer term, we would need to identify the Azure AD attribute we need to update with UPS value through DIrSync. We tried “msRtcSipPrimaryUserAddress” but UPS service never picks up this value. We are suspecting the correct attribute might be proxyAddresses but would need to confirm with Microsoft.
 
See Ultimate procedure to display SharePoint online hybrid search results in SharePoint Server 2013 for other steps to configure hybrid search. 
 

No comments:

Post a Comment