Thursday, January 19, 2017

Resolved - Failed to configure MIIS post database

After installing SharePoint 2013 SP1 CU 201609 on DEV server, "User Profile Synchronization Service" stopped working. ULS log shows the error message below.

UserProfileApplication.SynchronizeMIIS: Failed to configure MIIS post database, will attempt during next rerun. Exception: System.Configuration.ConfigurationErrorsException: ERR_START_SERVICE    
 at Microsoft.Office.Server.UserProfiles.Synchronization.ILMPostSetupConfiguration.ValidateConfigurationResult(UInt32 result)    
 at Microsoft.Office.Server.Administration.UserProfileApplication.SetupSynchronizationService(ProfileSynchronizationServiceInstance profileSyncInstance).

I tried all possible solutions through online research, but, none of them works.

In the end, I submitted a Microsoft support ticket.  It turns out that we have to remove "Forefront Identity Manager Service" from the dependency of windows service "FIMSynchronizationService"


Create a ".reg" file, then add the text below to it, then merge this reg file into the Windows of SharePoint server.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\FIMSynchronizationService]
"DependOnService"=hex(7):57,00,69,00,6e,00,6d,00,67,00,6d,00,74,00,00,00,00,00

Then reboot the server. Below is the result.



Now we can start the SharePoint service instance "User Profile Synchronization Service".

I hope this post can save you some headache.


PS: Microsoft support team said they never got this issue before. So I assume it only happens when SharePoint and Database are installed on the same server.

Tuesday, January 17, 2017

Why Microsoft web sites are so slow?

It's always slow, especially the MSDN web sites.  Why?

Below is a screenshot from the document "Office 365 for IT Pros.201611"(Page 73). It explains the passive authentication flow, which is the default user authentication mechanism for web browsers.

In theory, after log in, the user credential/ticket is cached, and the rest operation should be faster. But unfortunately, there are a lot of dynamic data loading on MSDN, which may triggered other authentication events.

Maybe that's the reason.

PS: why Google web sites are much faster?


Tuesday, December 20, 2016

Move SharePoint 2013 search server in one go

There are many posts about how to move search components from one server to another. But I cannot find a complete version to do that in one go.

It's frustrating to keep "waiting" for the current step to complete, so we can start next step. That's why I build one as below. This one assumes that the farm only has one search server which hosts all search components. And it moves all search components from the current server to a new SharePoint server.

The whole process may take more than 20 minutes.

Most of these PowerShell scripts are copied from here, thanks for the hard work of Matt Milsark.


function MoveSPSearchComponents([string]$SearchServerNameOld, [string]$SearchServerNameNew)
{
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - All 6 Search components are going to be moved from server '$SearchServerNameOld' to '$SearchServerNameNew'" -f DarkYellow
    # START THE SEARCH SERVICE ON THE NEW SERVER
    $ssa = Get-SPEnterpriseSearchServiceApplication
    $active = Get-SPEnterpriseSearchTopology -SearchApplication $ssa –Active
    $clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone –SearchTopology $active

    $instances = @(Get-SPEnterpriseSearchServiceInstance -Identity $SearchServerNameNew | Where-Object {$_.Status -eq "Online"})
    if ($instances.Count -eq 0)
    {
        Start-SPEnterpriseSearchServiceInstance -Identity $SearchServerNameNew
     
        do
        {
            $instances = @(Get-SPEnterpriseSearchServiceInstance -Identity $SearchServerNameNew | Where-Object {$_.Status -eq "Online"})
            Write-Host "." -NoNewLine
            Start-Sleep -s 1
        } until ($instances.Count -gt 0)
        Write-Host "Search instance on server '$SearchServerNameNew' is Online now" -f DarkGreen
    }

    $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameNew})
    $SearchComponents
    $SearchComponents | Remove-SPEnterpriseSearchComponent -SearchTopology $clone -confirm:$false
    Set-SPEnterpriseSearchTopology -Identity $clone

    do
    {
        Write-Host "." -NoNewLine
        Start-Sleep -s 1
        $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameNew})
    } until ($SearchComponents.Count -eq 0)
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - Search components on server '$SearchServerNameNew' is cleaned up" -f DarkGreen
 
    # CREATE NEW SEARCH COMPONENTS ON THE NEW SEARCH SERVER
    $active = Get-SPEnterpriseSearchTopology -SearchApplication $ssa -Active
    $clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone –SearchTopology $active
 
    New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology $clone -SearchServiceInstance $SearchServerNameNew
    New-SPEnterpriseSearchAnalyticsProcessingComponent -SearchTopology $clone -SearchServiceInstance $SearchServerNameNew
    New-SPEnterpriseSearchContentProcessingComponent -SearchTopology $clone -SearchServiceInstance $SearchServerNameNew
    New-SPEnterpriseSearchCrawlComponent -SearchTopology $clone -SearchServiceInstance $SearchServerNameNew
    New-SPEnterpriseSearchIndexComponent -SearchTopology $clone -IndexPartition 0 -SearchServiceInstance $SearchServerNameNew

    Set-SPEnterpriseSearchTopology -Identity $clone

    do
    {
        Write-Host "." -NoNewLine
        Start-Sleep -s 1
        $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameNew})
    } until ($SearchComponents.Count -eq 5)
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - 5 Search components are created on server '$SearchServerNameNew' in the clone topology" -f DarkGreen

    # REMOVE COMPONENTS FROM ORGINAL SEARCH SERVER
    $active = Get-SPEnterpriseSearchTopology -SearchApplication $ssa -Active
    $clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone –SearchTopology $active

    $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameOld -and $_.Name -NotMatch 'AdminComponent'})
    $SearchComponents | Remove-SPEnterpriseSearchComponent -SearchTopology $clone -confirm:$false
 
    Set-SPEnterpriseSearchTopology -Identity $clone
 
    do
    {
        Write-Host "." -NoNewLine
        Start-Sleep -s 1
        $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameOld -and $_.Name -NotMatch 'AdminComponent'})
    } until ($SearchComponents.Count -eq 0)
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - Search components on server '$SearchServerNameOld' are removed (except the admin component)" -f DarkGreen
 
    # START AN ADMIN COMPONENT ON THE NEW SERVER
    $active = Get-SPEnterpriseSearchTopology -SearchApplication $ssa –Active
    $clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone –SearchTopology $active

    #$NewServer = Get-SPEnterpriseSearchServiceInstance -Identity $SearchServerNameNew
    New-SPEnterpriseSearchAdminComponent -SearchTopology $clone -SearchServiceInstance $SearchServerNameNew
    Set-SPEnterpriseSearchTopology -Identity $clone
 
    do
    {
        Write-Host "." -NoNewLine
        Start-Sleep -s 1
        $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameNew -and $_.Name -match 'AdminComponent'})
    } until ($SearchComponents.Count -eq 1)
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - Search Admin components on server '$SearchServerNameNew' is created" -f DarkGreen
 
    # DELETE THE OLD ADMIN COMPONENT
    $active = Get-SPEnterpriseSearchTopology -SearchApplication $ssa -Active
    $clone = New-SPEnterpriseSearchTopology -SearchApplication $ssa -Clone –SearchTopology $active

    $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameOld -and $_.Name -match 'AdminComponent'})
    $SearchComponents | Remove-SPEnterpriseSearchComponent -SearchTopology $clone -confirm:$false
    Set-SPEnterpriseSearchTopology -Identity $clone

    do
    {
        Write-Host "." -NoNewLine
        Start-Sleep -s 1
        $SearchComponents = @(Get-SPEnterpriseSearchComponent -SearchTopology $clone | Where-Object {$_.ServerName -eq $SearchServerNameOld -and $_.Name -match 'AdminComponent'})
    } until ($SearchComponents.Count -eq 0)
    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - Search Admin components on server '$SearchServerNameOld' is removed" -f DarkGreen

    Write-Host "$(get-date -UFormat '%Y%m%d %H:%M:%S') - All 6 Search components are moved from server '$SearchServerNameOld' to '$SearchServerNameNew'" -f DarkYellow
}

$SearchServerNameNew = $env:COMPUTERNAME
MoveSPSearchComponents "OldSearchServerName" $SearchServerNameNew

Monday, December 12, 2016

Best simple guide about SharePoint search

Found an excellent post about SharePoint Search

It covers all major parts, and can be read through in a few minutes.

Strongly recommend it!

All credit goes to icansharepoint


Tuesday, December 6, 2016

No way to host server side code in container

When "SharePoint Framework" was released, I was pretty sure that, sooner or later, Microsoft would host server side code in containers (http://fangdahai.blogspot.com.au/2016/05/the-future-of-sharepoint-development.html)

After a few days of testing the container feature on Windows Server 2016, I realised that it was never going to happen :-(

The installation media of SQL Server Express 2016 with SP1 is around 421 MB (64 bit). After put it into windows container, how much disk space it takes? 10 GB!

The post Windows Containers Compared: WinDocks vs. Microsoft is correct. There is something wrong with "Windows Container" by design. To me, "Windows Container" is more like "Windows Nano Server" virtual machine.

Friday, November 25, 2016

"This item is no longer available" when trying to approve master page changes

After some minor change of a master page in SharePoint designer, I checked it in as a major version. SharePoint designer then opened the library "Master Page Gallery" ( /_catalogs/masterpage/Forms/my-sub.aspx ).

I can see the two changed master page files ( .html and .master ) in the "My submissions" list view. However, when trying to open the context menu to approve it, I got the error message "This item is no longer available".


I logged on as SharePoint farm administrator, so it should not be caused by permission issue.

Google leads me to this one, but it doesn't help.

In the end, I changed the versioning settings of this library, which fixed the problem.



Then I changed the settings back.

This is the first time I got this issue in the past 9 years. I guess it's caused by a minor bug. As alternative solution, it's good enough.

The environment is SharePoint 2016 with CU 201611.

Wednesday, November 23, 2016

How to make Chrome support SSO, and enable CORS

Recently I migrated some SharePoint web parts from C# to JavaScript + HTML

Everything works well in IE 11 after enabling CORS.

But, when test it in Chrome 54, I got the error message below, and it constantly prompt for user name and password..

ERR_INVALID_HANDLE: "This site can’t be reached"

IIS log says it's requested by anonymous user.

After days of struggling, it turns out not as easy as it looks like.  We need to do the following steps.

1. IE -> internet options -> security -> Local Intranet zone

Add SharePoint Server and the Web App Server to "Local Intranet zone". So IE and Chrome will try to use the current windows user credential to log on web server.

This enables NTLM authentication on SharePoint and Web App Server.

Reference: https://sysadminspot.com/windows/google-chrome-and-ntlm-auto-logon-using-windows-authentication/

2. Configure Delegated Security in Google Chrome

Need to add server names as below to registry table on client computer.

We can do it through Group Policy.

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome]
"AuthNegotiateDelegateWhitelist"="*.DomainName.local"
"AuthSchemes"="digest,ntlm,negotiate"
"AuthServerWhitelist"="*.DomainName.local"

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Policies\Google\Chrome]
"AuthNegotiateDelegateWhitelist"="*.DomainName.local"
"AuthSchemes"="digest,ntlm,negotiate"
"AuthServerWhitelist"="*.DomainName.local"

This enables NTLM authentication and Kerberos on SharePoint and Web App Server.

Reference: https://specopssoft.com/configuring-chrome-and-firefox-for-windows-integrated-authentication/

3. Configure Kerberos

Set up SPN for both the SharePoint Server and the Web App Server.

Reference: https://support.microsoft.com/en-au/kb/929650

4. Change Startup.cs a bit in Configure() to handle preflight requests

This is for CORS.

Reference: http://stackoverflow.com/questions/38838006/asp-net-mvc-core-cors

5. Enable Anonymous Authentication on Web App Server

This is for CORS.

6. If Kestrel Server is not running, we need to submit "GET" request first.

It cannot be started up by "Preflight Options" request. It seems like a bug.

[update 2016-11-29] 7. To make things easier, add the settings below to the web.config file of Web App Server. 

This helps to enable CORS.

<httpProtocol>
  <customHeaders>
<clear />
<add name="Access-Control-Allow-Origin" value="http://SharePointSiteUrl" />
<add name="Access-Control-Allow-Headers" value="Authorization, X-Requested-With, Content-Type, Origin, Accept, X-Auth-Token" />
<add name="Access-Control-Allow-Methods" value="*" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Max-Age" value="60" />
  </customHeaders>
</httpProtocol>


Done.

=================

Test Environment.

Client side: Chrome 54 + JavaScript + JQuery 3.1.1

Server side: SharePoint Server 2016 CU201611 + Content Editor Web Part

Web App Server: IIS 8.5 + Asp.Net Core Web API 2 + C#

JavaScript + JQuery 3.1.1 (based on JSON, in a Content Editor Web Part)

var strUrl = "http://WebService2016dev.DomainName.local/SSO/AppTest1/api/SSOAuthentication";

var JSONObject= {"Key": "db25f36b-fa81-4c8e-9af5-9c8468ce8a79",
   "UserLoginName": "domain\\UserLoginName",
   "ReturnCode": "",
   "ReturnValue": "",
   "ReturnDetailedInfo": "" };
var jsonData = JSON.stringify(JSONObject);

$.support.cors = true;

var strMethodType = 'GET';
var strContentType = 'text/plain';

$.ajax( {
url: strUrl,
type: strMethodType,
contentType: strContentType ,
xhrFields: {
withCredentials: true
},
data: '',
dataType: "json",
async: false,
crossDomain: true,
success: function( response ) {
console.log("GET success - Data from Server: " + JSON.stringify(response));
},
error: function( request, textStatus, errorThrown ) {
console.log("GET error - You can not send Cross Domain AJAX requests: textStatus=" + textStatus + ", errorThrown=" + errorThrown);
console.log(request.responseText);
}
} );

var strMethodType = 'POST';
var strContentType = 'application/json; charset=utf-8';

$.ajax( {
url: strUrl,
type: strMethodType,
contentType: strContentType ,
xhrFields: {
withCredentials: true
},
data: jsonData,
dataType: "json",
crossDomain: true,
success: function( response ) {
console.log("POST success - Data from Server: " + JSON.stringify(response));
},
error: function( request, textStatus, errorThrown ) {
console.log("POST error - You can not send Cross Domain AJAX requests: textStatus=" + textStatus + ", errorThrown=" + errorThrown);
console.log(request.responseText);
}
} );

Web App Server (IIS 8.5 + Asp.Net Core Web API 2), main C# code in Startup.cs

app.Use(async (httpContext, next) =>
{
await next();
if (httpContext.Request.Path.Value.Contains(@"api/") && httpContext.Request.Method == "OPTIONS")
{
httpContext.Response.StatusCode = StatusCodes.Status204NoContent;
}
});

app.UseMvc();