Friday, April 20, 2018

List Azure AD groups via CSOM in Powershell

Recently I showed how to list Azure AD/O365 groups in Powershell using REST Graph API: see List Azure AD groups via Rest Graph API in Powershell. In this post I will show how to do the same thing using CSOM. It may be interesting especially in part of how to obtain access token based on client id and client secret via CSOM and how it differs from REST approach.

In order to fully understand the code you may also need to check another article: Avoiding StackOverflowException when use assembly binding redirect in PowerShell. It shows how to use C#-based assembly binding redirect in PowerShell (native Powershell assembly binding redirect which is described here Use specific version of Sharepoint client object model in PowerShell via assembly binding redirection causes StackOverflowException when you try to use Graph API).

So here is the script:

param
(
	[Parameter(Mandatory=$true)]
	$Tenant,
	[Parameter(Mandatory=$true)]
	$ClientId,
	[Parameter(Mandatory=$true)]
	$ClientSecret
)

$currentDir = Convert-Path(Get-Location)
$dllCommonDir = resolve-path($currentDir + "\..\Assemblies\Common\")
$pnpCoreDir = resolve-path($currentDir + "\..\packages\SharePointPnPCoreOnline.2.22.1801.0\lib\net45\")
$graphDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.1.7.0\lib\net45\")
$graphCoreDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.Core.1.7.0\lib\net45\")
$newtonJsonDir = resolve-path($currentDir + "\..\packages\Newtonsoft.Json.10.0.3\lib\net45\")

$AssemblyNewtonJson = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($newtonJsonDir, "Newtonsoft.Json.dll"))
$AssemblyGraphCore = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphCoreDir, "Microsoft.Graph.Core.dll"))
$AssemblyGraph = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphDir, "Microsoft.Graph.dll"))
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllCommonDir, "Microsoft.Identity.Client.dll"))
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($pnpCoreDir, "OfficeDevPnP.Core.dll"))

if (!("Redirector" -as [type]))
{
$source = 
@'
using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

public class Redirector
{
    public readonly string[] ExcludeList;

    public Redirector(string[] ExcludeList = null)
    {
        this.ExcludeList  = ExcludeList;
        this.EventHandler = new ResolveEventHandler(AssemblyResolve);
    }

    public readonly ResolveEventHandler EventHandler;

    protected Assembly AssemblyResolve(object sender, ResolveEventArgs resolveEventArgs)
    {
        //Console.WriteLine("Attempting to resolve: " + resolveEventArgs.Name); // remove this after its verified to work
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var pattern  = "PublicKeyToken=(.*)$";
            var info     = assembly.GetName();
            var included = ExcludeList == null || !ExcludeList.Contains(resolveEventArgs.Name.Split(',')[0], StringComparer.InvariantCultureIgnoreCase);

            if (included && resolveEventArgs.Name.StartsWith(info.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                if (Regex.IsMatch(info.FullName, pattern))
                {
                    var Matches        = Regex.Matches(info.FullName, pattern);
                    var publicKeyToken = Matches[0].Groups[1];

                    if (resolveEventArgs.Name.EndsWith("PublicKeyToken=" + publicKeyToken, StringComparison.InvariantCultureIgnoreCase))
                    {
                        //Console.WriteLine("Redirecting lib to: " + info.FullName); // remove this after its verified to work
                        return assembly;
                    }
                }
            }
        }

        return null;
    }
}
'@

    $type = Add-Type -TypeDefinition $source -PassThru 
}

try
{
    $redirector = [Redirector]::new($null)
    [System.AppDomain]::CurrentDomain.add_AssemblyResolve($redirector.EventHandler)
}
catch
{
    #.net core uses a different redirect method
    Write-Warning "Unable to register assembly redirect(s). Are you on ARM (.Net Core)?"
}

function GetAccessToken($tenant, $clientId, $clientSecret)
{
	$appCredentials = New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList $clientSecret
	$aadLoginUri = New-Object System.Uri -ArgumentList "https://login.microsoftonline.com/"
	$authorityUri = New-Object System.Uri -ArgumentList $aadLoginUri, $tenant
	$authority = $authorityUri.AbsoluteUri
	$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
	$clientApplication = New-Object Microsoft.Identity.Client.ConfidentialClientApplication($clientId, $authority, $redirectUri, $appCredentials, $null, $null)
	[string[]]$defaultScope = @("https://graph.microsoft.com/.default")
	$authenticationResult = $clientApplication.AcquireTokenForClientAsync($defaultScope).Result
	return $authenticationResult.AccessToken
}

function ListGroups($accessToken)
{
	$existingGroups = [OfficeDevPnP.Core.Framework.Graph.UnifiedGroupsUtility]::ListUnifiedGroups($accessToken, "", "")
	foreach($group in $existingGroups)
	{
		Write-Host $group.DisplayName
	}
}

$accessToken = GetAccessToken $Tenant $ClientId $ClientSecret
ListGroups $accessToken

Tuesday, April 17, 2018

List Azure AD groups via Rest Graph API in Powershell

The following PowerShell snipped shows how to acquire acces token based on client id/client secret via REST Graph API and list all Azure AD groups in Powershell:

param
(
	[Parameter(Mandatory=$true)]
	[string]$Tenant,
	[Parameter(Mandatory=$true)]
	[string]$ClientId,
	[Parameter(Mandatory=$true)]
	[string]$ClientSecret
)

$currentDir = [System.IO.Directory]::GetCurrentDirectory()
$dllCommonDir = resolve-path($currentDir + "\..\..\Assemblies\Common\")
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllCommonDir, "Microsoft.Identity.Client.dll"))

function GetAccessToken($tenant, $clientId, $clientSecret)
{
	$appCredentials = New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList $clientSecret
	$aadLoginUri = New-Object System.Uri -ArgumentList "https://login.microsoftonline.com/"
	$authorityUri = New-Object System.Uri -ArgumentList $aadLoginUri, $tenant
	$authority = $authorityUri.AbsoluteUri
	$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
	$clientApplication = New-Object Microsoft.Identity.Client.ConfidentialClientApplication($clientId, $authority, $redirectUri, $appCredentials, $null, $null)
	[string[]]$defaultScope = @("https://graph.microsoft.com/.default")
	$authenticationResult = $clientApplication.AcquireTokenForClientAsync($defaultScope).Result
	return $authenticationResult.AccessToken
}

function RetrieveGroupsRest($accessToken)
{
	$authHeader = @{
		"Content-Type"="application\json"
		"Authorization"="Bearer " + $accessToken
		}

	$uri = "https://graph.microsoft.com/v1.0/groups"
    $result = @()
    do{
        $objects = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method Get
        $uri = $objects.'@odata.nextlink'
        $result = $result + $objects.value
       
    }until ($uri -eq $null)
	return $result
}

$accessToken = GetAccessToken $Tenant $ClientId $ClientSecret
$dataFromGraphAPI = RetrieveGroupsRest $accessToken
$dataFromGraphAPI | ft -Property id,displayName

Thursday, April 5, 2018

Fix error Failed to call GetTypes on assembly Microsoft.Office.TranslationServices after installing Sharepoint 2013 March 2013 CU

After installing Sharepoint 2013 March 2013 CU (and higher) you may get the following error when try to do something in the site:

Failed to call GetTypes on assembly Microsoft.Office.TranslationServices, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c. Method not found

In order to fix it try the following steps:

1. On your Sharepoint server go to the GAC and find folder of Microsoft.Office.TranslationServices.dll assembly (e.g. C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.Office.TranslationServices\v4.0_15.0.0.0__71e9bce111e9429c)

2. Open folder in Explorer > right click on Microsoft.Office.TranslationServices.dll > Properties > Details and check actual assembly version:

2018-04-05_10-16-33

in this example full version is 15.0.4420.1017.

3. Go to virtual folder of your Sharepoint site (C:\inetpub\wwwroot\wss\VirtualDirectories\{Site})

4. Edit web.config > find <runtime>/<assemblyBinding> section and add the following section to the end:

<dependentAssembly xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity name="Microsoft.Office.TranslationServices" publicKeyToken="71e9bce111e9429c" culture="neutral" />
  <bindingRedirect oldVersion="1.0.0.0-15.0.0.0" newVersion="15.0.4420.1017" />
</dependentAssembly>
<dependentAssembly xmlns="urn:schemas-microsoft-com:asm.v1">
  <assemblyIdentity name="Microsoft.Office.TranslationServices.ServerStub" publicKeyToken="71e9bce111e9429c" culture="neutral" />
  <bindingRedirect oldVersion="1.0.0.0-15.0.0.0" newVersion="15.0.4420.1017" />
</dependentAssembly>

Note that it is important to specify both assemblies - if you will only specify Microsoft.Office.TranslationServices you will still get same error when SharePoint will try to call /_vti_bin/client.svc (which in turn may cause other unclear problems like error "The server was unable to save the form at this time. Please try again" when you try to create or edit list item). After that error should disappear. If similar error will occur for different assembly try to add assembly bindnig redirection for it as well.

Wednesday, March 28, 2018

Avoid problem with redirecting Sharepoint sites which use .dev sub domains to https in Chrome and Firefox

As you probably heard some time ago Chrome and FF started to redirect sites which use .dev sub domains in URL to https. Issue is described e.g. here: Chrome & Firefox now force .dev domains to HTTPS via preloaded HSTS. Briefly .dev domain is owned by Google and they added HSTS rule for it which forces redirection to https. You may check it in Chrome: open chrome://net-internals#hsts and make query for dev domain:

2018-03-28_16-38-32

As you can see there is FORCE_HTTPS rule.

This problem may occur in local dev Sharepoint environments which often use .dev domain. So if you have e.g. site http://intranet.sp.dev and will try to open it in Chrome or Firefox it won’t open it with unclear error:

2018-03-28_16-37-56

But if you notice url address bar of the browser you will find that it actually tries to open https://intranet.sp.dev instead of http://intranet.sp.dev and it causes issue. You may try to add https binding to the site using self-signed certificate, but browser will still show error that connection is not secure.

The following solution may be used for avoiding this problem with Sharepoint site. It requires using new site address http://intranet.sp.local for your Sharepoint site instead of http://intranet.sp.dev:

  • add intranet.sp.local binding for your Sharepoint site in IIS Manager
  • in C:/Windows/System32/drivers/etc/host add
      127.0.0.1 intranet.sp.local
  • in Central administration > Alternate access mappings > Add internal url for not used zone: http://intranet.sp.local

After that site can be opened in Chrome and FF using http://intranet.sp.local url.

Monday, March 26, 2018

Avoiding StackOverflowException when use assembly binding redirect in PowerShell

Recently I faced with interesting problem with assembly binding redirect in PowerShell: in my script I needed to use the following versions of the assemblies (these exact versions were used in other components and I couldn’t add another versions because of number of reasons):

OfficeDevPnP.Core 2.22.1801.0
Microsoft.Graph 1.7.0.0
Microsoft.Graph.Core 1.7.0.0

The problem is that OfficeDevPnP.Core 2.22.1801.0 references different versions of Microsoft.Graph and Microsoft.Grap.Core:

Microsoft.Graph 1.1.1.0
Microsoft.Graph.Core 1.2.1.0

So I needed to use assembly binding redirect. At first I tried approach described in the following post: Use specific version of Sharepoint client object model in PowerShell via assembly binding redirection:

$currentDir = Convert-Path(Get-Location)
$pnpCoreDir = resolve-path($currentDir + "\..\packages\SharePointPnPCoreOnline.2.22.1801.0\lib\net45\")
$graphDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.1.7.0\lib\net45\")
$graphCoreDir = resolve-path($currentDir + "\..\packages\Microsoft.Graph.Core.1.7.0\lib\net45\")

$AssemblyGraphCore = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphCoreDir, "Microsoft.Graph.Core.dll"))
$AssemblyGraph = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphDir, "Microsoft.Graph.dll"))
[System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($pnpCoreDir, "OfficeDevPnP.Core.dll"))


<#$OnAssemblyResolve = [System.ResolveEventHandler] {
	param($sender, $e)
	
	if ($e.Name.StartsWith("Microsoft.Graph,"))
	{
		$AssemblyGraph = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphDir, "Microsoft.Graph.dll"))
		return $AssemblyGraph
	}
	if ($e.Name.StartsWith("Microsoft.Graph.Core,"))
	{
		$AssemblyGraphCore = [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($graphCoreDir, "Microsoft.Graph.Core.dll"))
		return $AssemblyGraphCore
	}

	foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
	{
		if ($a.FullName -eq $e.Name)
		{
		  return $a
		}
	}
	Write-Host "Return null" -foregroundcolor red
	return $null
}
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)

However attempt to call method from loaded assemblies caused StackOverflowException and closing PowerShell session.

After that I tried C#-based assembly redirector (found it in the following forum thread: Powershell - Assembly binding redirect NOT found in application configuration file):

if (!("Redirector" -as [type]))
{
$source = 
@'
using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

public class Redirector
{
    public readonly string[] ExcludeList;

    public Redirector(string[] ExcludeList = null)
    {
        this.ExcludeList  = ExcludeList;
        this.EventHandler = new ResolveEventHandler(AssemblyResolve);
    }

    public readonly ResolveEventHandler EventHandler;

    protected Assembly AssemblyResolve(object sender, ResolveEventArgs resolveEventArgs)
    {
        Console.WriteLine("Attempting to resolve: " + resolveEventArgs.Name); // remove this after its verified to work
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            var pattern  = "PublicKeyToken=(.*)$";
            var info     = assembly.GetName();
            var included = ExcludeList == null || !ExcludeList.Contains(resolveEventArgs.Name.Split(',')[0], StringComparer.InvariantCultureIgnoreCase);

            if (included && resolveEventArgs.Name.StartsWith(info.Name, StringComparison.InvariantCultureIgnoreCase))
            {
                if (Regex.IsMatch(info.FullName, pattern))
                {
                    var Matches        = Regex.Matches(info.FullName, pattern);
                    var publicKeyToken = Matches[0].Groups[1];

                    if (resolveEventArgs.Name.EndsWith("PublicKeyToken=" + publicKeyToken, StringComparison.InvariantCultureIgnoreCase))
                    {
                        Console.WriteLine("Redirecting lib to: " + info.FullName); // remove this after its verified to work
                        return assembly;
                    }
                }
            }
        }

        return null;
    }
}
'@

    $type = Add-Type -TypeDefinition $source -PassThru 
}

try
{
    $redirector = [Redirector]::new($null)
    [System.AppDomain]::CurrentDomain.add_AssemblyResolve($redirector.EventHandler)
}
catch
{
    #.net core uses a different redirect method
    Write-Warning "Unable to register assembly redirect(s). Are you on ARM (.Net Core)?"
}

And surprisingly this approach worked. It looks like a bug in PowerShell script-based assembly binding redirect. Hope it will help someone.

Wednesday, March 21, 2018

How to reset credentials for Sharepoint Designer

Here are instructions of how to reset credentials for Sharepoint Designer:

1. Go to Windows control panel and select User accounts:

01

2. In opened window click Manage your credentials:

02

3. Then choose Windows credentials:

03

4. On credentials window under Generic credentials there will be account which start with “MicrosoftOffice15_Data:” prefix (this is a prefix for Sharepoint Designer 2013. For other versions most probably prefix will be different):

04

Find those which was cached for your site and remove it from the list.

After that if you will open this site again in Sharepoint Designer it will ask to enter credentials.

Thursday, March 15, 2018

How to renew expired app in Sharepoint Online

As you probably know when you register new app in Sharepoint (using /_layouts/15/AppRegNew.aspx) it’s expiration date is set to 1 year from moment of registration. If your app is expired perform the following steps in order to renew it on 3 years:

1. Run Windows PowerShell and connect to Msol:

Connect-MsolService

2. Get list of all apps:

Get-MsolServicePrincipal -all | Where-Object -FilterScript { ($_.DisplayName -notlike "*Microsoft*") -and ($_.DisplayName -notlike "autohost*") -and  ($_.ServicePrincipalNames -notlike "*localhost*") } | Out-File log_apps.txt -Append

3. From generated log_apps.txt copy AppPrincipalId for expired app

4. Get list of all principals:

Get-MsolServicePrincipalCredential -AppPrincipalId {copied_app_principal_id} -ReturnKeyValues $true | Out-File log_principals.txt -Append
5. Check end dates for app principals. If they are expired run the following script which will generate new client secret and renew principals on 3 years:
# start script
$clientId = "{copied_app_principal_id}"
$bytes = New-Object Byte[] 32
$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rand.GetBytes($bytes)
$rand.Dispose()
$newClientSecret = [System.Convert]::ToBase64String($bytes)
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Sign -Value $newClientSecret -StartDate (Get-Date) -EndDate (Get-Date).AddYears(3)
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Verify -Value $newClientSecret -StartDate (Get-Date) -EndDate (Get-Date).AddYears(3)
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Password -Usage Verify -Value $newClientSecret -StartDate (Get-Date) -EndDate (Get-Date).AddYears(3)
Write-Host "New client secret:"
$newClientSecret
# end script

Depending on permissions which are required for your app you may need to run the last script under tenant admin account (if app requires tenant full access).

Tuesday, March 13, 2018

Fix missing usage analytics logs in Sharepoint 2013 event store

Sharepoint 2013 usage analytics reports give to administrators view of site usage statistics. But it is not so simple to configure them to work. Most frequent issue is that Excel reports contain only zeros. There may be many reasons for this problem. One of them is missing log files in EventStore. EventStore is located in "C:\Program Files\Microsoft Office Servers\15.0\Data\Office Server\Analytics_{GUID}\EventStore\" folder on the server. If it is empty check the following in PowerShell:

$aud = Get-SPUsageDefinition | where {$_.Name -like "Analytics*"}
$aud | fl

It will show something like this:

01

Pay attention on EnableReceivers and Receivers – first should be set to True and second to Microsoft.Office.Server.Search.Analytics.Internal.AnalyticsCustomRequestUsageReceiver like shown on the picture. If EnableReceivers is set to False and Receivers is empty execute the following code also in PowerShell:

$aud.Receivers.Add("Microsoft.Office.Server.Search.Applications, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c","Microsoft.Office.Server.Search.Analytics.Internal.AnalyticsCustomRequestUsageReceiver")
$aud.EnableReceivers = $true
$aud.Update()

After that check also requests usage:

$prud = Get-SPUsageDefinition | where {$_.Name -like "Page Requests"}
$prud | fl

Which should look like this:

02

Here also if EnableReceivers is set to False and Receivers is empty execute the following code:

$prud.Receivers.Add("Microsoft.Office.Server.Search.Applications, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", "Microsoft.Office.Server.Search.Analytics.Internal.ViewRequestUsageReceiver")
$prud.EnableReceivers = $true
$prud.Update()

After that go to the site and click some links to generate traffic. Log files should be created in EventStore folder.

Thursday, March 1, 2018

How to check does site collection exist by absolute url using CSOM in Sharepoint

Suppose that we need to check whether site collection with specified absolute url exists or not using client side object model. In OfficeDevPnP library there is convenient extension method for ClientContext WebExtensions.WebExistsFullUrl:

        public static bool WebExistsFullUrl(this ClientRuntimeContext context, string webFullUrl)
        {
            bool exists = false;
            try
            {
                using (ClientContext testContext = context.Clone(webFullUrl))
                {
                    testContext.Load(testContext.Web, w => w.Title);
                    testContext.ExecuteQueryRetry();
                    exists = true;
                }
            }
            catch (Exception ex)
            {
                if (IsUnableToAccessSiteException(ex) || IsCannotGetSiteException(ex))
                {
                    exists = true;
                }
            }
            return exists;
}

Unfortunately this method works properly only within single site collection i.e. when client context (which is extended with this extension method) and url to check belong to the same managed path.

Example:
context is created from the root site http://example.com. This site has http://example.com/test sub site. In this case WebExistsFullUrl returns true for http://example.com/test url and false for some non-existent sub site's url like http://example.com/test123. I.e. behavior is correct.

But if context and url to check belong to different managed path then it always returns true.

Example:
context is created from the root site http://example.com and we call WebExistsFullUrl for some url which may belong to other site collection (e.g. which uses different managed path like http://example.com/teams/some-not-real-url. Suppose that at the moment of call we don't know whether this collection exists or not - we want to determine it by WebExistsFullUrl call). In this case WebExistsFullUrl returns true even if site collection with specified url doesn't exists.

When I analyzed the code I found the following. When we call this method with context and url which belong to the same managed path it throws exception like expected. But when it is called with context and url which belong to different managed paths exception is not thrown. Instead context is created for the root site http://example.com and method returns true and caller thinks that site exists. In order to fix this problem for different managed paths I applied the following fix in our local version:

        public static bool WebExistsFullUrl(this ClientRuntimeContext context, string webFullUrl)
        {
            bool exists = false;
            try
            {
                using (ClientContext testContext = context.Clone(webFullUrl))
                {
                    testContext.Load(testContext.Web, w => w.Title, w => w.Url);
                    testContext.ExecuteQueryRetry();
                    exists = (string.Compare(testContext.Web.Url, webFullUrl, true) == 0);
                }
            }
            catch (Exception ex)
            {
                if (IsUnableToAccessSiteException(ex) || IsCannotGetSiteException(ex))
                {
                    // Site exists, but you don't have access .. not sure if this is really valid
                    // (I guess if checking if URL is already taken, e.g. want to create a new site
                    // then this makes sense).
                    exists = true;
                }
            }
            return exists;
}

I.e. before to return true method also checks whether loaded url is the same as url to be checked. If they are different (which happens in scenario with different managed paths) it will return false like it should.

Described behavior was found when root site collection (http://example.com) was host-named site collection, but most probably it will also work same way for regular site collections.

This problem is also submitted to OfficeDevPnP Core issues section on GitHub: WebExtensions.WebExistsFullUrl returns true for non-existent sites from different managed path in Sharepoint 2013 on-premise.

Wednesday, February 28, 2018

One reason for “This operation can be performed only on a computer that is joined to a server farm” error in Sharepoint

If you faced with the following error when try to open Sharepoint 2013 site:

This operation can be performed only on a computer that is joined to a server farm by users who have permissions in SQL Server to read from the configuration database. To connect this server to the server farm, use the SharePoint Products Configuration Wizard, located on the Start menu in Microsoft SharePoint 2010 Products.

Stack trace:

Microsoft.SharePoint.Utilities.SPUtility.AlternateServerUrlFromHttpRequestUrl(Uri url) +262
Microsoft.SharePoint.Administration.SPAlternateUrl.GetContextUri(HttpContext ctx) +385
Microsoft.SharePoint.SPAppRequestContext.InitCurrent(HttpContext context) +976
Microsoft.SharePoint.SPAppRequestContext.get_Current() +175
Microsoft.SharePoint.SPGlobal.CreateSPRequestAndSetIdentity(SPSite site, String name, Boolean bNotGlobalAdminCode, String strUrl, Boolean bNotAddToContext, Byte[] UserToken, SPAppPrincipalToken appPrincipalToken, String userName, Boolean bIgnoreTokenTimeout, Boolean bAsAnonymous) +400
Microsoft.SharePoint.SPRequestManager.GetContextRequest(SPRequestAuthenticationMode authenticationMode) +120
Microsoft.SharePoint.Administration.SPFarm.get_RequestAny() +370
Microsoft.SharePoint.SPLanguageSettings.GetGlobalInstalledLanguages(Int32 compatibilityLevel) +39
Microsoft.SharePoint.Administration.SPTemplateFileSystemWatcher.RefreshInstalledLocales() +103
Microsoft.SharePoint.Administration.SPTemplateFileSystemWatcher.Initialize() +130
Microsoft.SharePoint.ApplicationRuntime.SPRequestModule.System.Web.IHttpModule.Init(HttpApplication app) +873
System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers) +582
System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context) +322
System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +384
System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +397
System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +646
System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +159
System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +771

Before to run Sharepoint Configuration Wizard go to Windows services and ensure that SQL Server service is running there. Sharepoint shows this error also when SQL server is stopped.

Saturday, February 24, 2018

How to use SyntaxHighlighter in Google Blogspot blogger engine: manual installation

For writing my previous posts I used Windows Live Write app. It has good enough syntax highlight plugin – it was not perfect but was good enough for my purposes – you may check example e.g. in the following post: One way to avoid “Term update failed because of save conflict” error when create managed metadata terms in Sharepoint. As you probably know MS discontinued Windows Live Writer some time ago and now there is it’s open source version called Open Live Write (which I use now also for writing this post). Unfortunately previous syntax highlight plugin doesn’t work and I needed to search alternative.

One of the first plugins which Google showed me was Windows/Open Live Writer Code Plugin. But as you may see on this page it says

The Live Writer Source Code Plug-in For WordPress.com Blogs.

When I tried to use it with my Blogspot blog it didn’t work.

There is popular source code highlighter library SyntaxHighlighter from Alex Gorbatchev. Unfortunately currently there is no available plugin for Open Live Writer which would utilize SyntaxHighlighter, but I found a way how to install it manually and want to share it with you. Of course this approach is not so convenient as using plugin but it is better than nothing.

First of all I would like to mention that at the moment of writing this post official project build script on GitHub doesn’t work because of the following issue: Building: loadReposFromCache(...).error is not a function. So I will describe manual installation.

1. First of all you need to encode your source code to replace < and > by &lt; and &gt; e.g. using this page: https://opinionatedgeek.com/Codecs/HtmlEncoder.

2. Then put your source code within <pre class="brush: {brush_name}"></pre> tags (you need to use Source table of Open Live Writer for that). Here {brush_name} should be replaced with brush name for used programming language. List of supported brushes can be found here: https://github.com/syntaxhighlighter/syntaxhighlighter/wiki/Brushes-and-Themes. Click on your programming language title and found brush.js file – if you will scroll it you will find exact brush aliases which can be used in class attribute. E.g. for C#:

Brush.aliases = ['c#', 'c-sharp', 'csharp'];

So for C# and XML tags will be the following:

<pre class="brush: csharp"></pre>
<pre class="brush: xml"></pre>

3. Add the following css and script references to the source tab of your post on top:

<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'>
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shAutoloader.js' type='text/javascript'></script>

These 4 references are common – they will be needed for all your posts. After them add references to needed brushes (which programming languages will be used in your post. List of exact file names can be found here: https://cdnjs.com/libraries/SyntaxHighlighter). E.g. if we use XML, javascript, css and C# we need to add the following references:
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'></script>

If you don’t want to use CDN from http://alexgorbatchev.com you may copy these files to your Google Drive and use them like described in the following article: http://www.bloggerseolab.com/2017/02/host-images-javascript-and-css-on-google-drive.html.

4. After above references add javascript call which will make exact highlight:

<script type="text/javascript">
  document.addEventListener("DOMContentLoaded", function(event) { 
    SyntaxHighlighter.highlight();
  });
</script>

After that publish your post and enjoy of syntax highlighting). In this post I used same method for highlighting last code sample so you may see how it works.

Friday, February 23, 2018

Camlex 4.3 and Camlex.Client 2.3 for Sharepoint are released

I’m glad to announce that today next versions of Camlex open source library have been releases:

  • Camlex 4.3 – for basic Sharepoint server object model
  • Camlex.Client 2.3 – for client-side object model (CSOM)

Camlex is a free library which simplifies creation of CAML queries (both static CAML queries when number of conditions is known at compile time and dynamic CAML queries when number of conditions will be known only in runtime).

In new version support for native float and decimal types was added. I.e. now you may use these types in queries explicitly without need to cast to double which was the only floating type supported before today’s release. And similar to double float and decimal values will be translated to Number type:

string caml = Camlex.Query().Where(x => (float)x["Rate"] == 1.5f).ToString();

will produce:

<Where>
  <Eq>
    <FieldRef Name="Rate" />
    <Value Type="Number">1.5</Value>
  </Eq>
</Where>

And with decimal:

string caml = Camlex.Query().Where(x => (decimal)x["Rate"] == 1.5m).ToString();

output result will be the same.

New packages are also updated in Nuget: they have Validating status now and will be available after some time when Nuget will complete antivirus checks.

And I’m especially glad today because this is the first release since Camlex got new home at GitHub https://github.com/sadomovalex/camlex (it was moved there last year from Codeplex since last one is discontinued now). Development of the library is on going and I will continue to do it more valuable for Sharepoint community in future as well.

Thursday, February 15, 2018

Change UI language for Windows 10 single language edition

Recently I needed to change language for Windows 10 single language edition. When PC was purchased it had non-English language installed and as it was single language edition (you may check it in My PC > Properties) it was not possible to change language from OS UI on English (for me it is simpler to have it on English). However at the end it became possible to change it from command line (see below). The most tricky part is to get correct version of needed language pack. In my case OS had 10.0.15063.0 build and correct language packs links for this build are listed here: Language Pack (Build 15063 – 1703). In case link won’t work here are the links:

Windows 10 build 15063 64-bit MUI/LPs

af-ZADownload link
am-ETDownload link
ar-SADownload link
as-INDownload link
az-Latn-AZDownload link
be-BYDownload link
bg-BGDownload link
bn-BDDownload link
bn-INDownload link
bs-Latn-BADownload link
ca-ESDownload link
ca-ES-valenciaDownload link
chr-CHER-USDownload link
cs-CZDownload link
cy-GBDownload link
da-DKDownload link
de-DEDownload link
el-GRDownload link
en-GBDownload link
en-USDownload link
es-ESDownload link
es-MXDownload link
et-EEDownload link
eu-ESDownload link
fa-IRDownload link
fi-FIDownload link
fil-PHDownload link
fr-CADownload link
fr-FRDownload link
ga-IEDownload link
gd-GBDownload link
gl-ESDownload link
gu-INDownload link
ha-Latn-NGDownload link
he-ILDownload link
hi-INDownload link
hr-HRDownload link
hu-HUDownload link
hy-AMDownload link
id-IDDownload link
ig-NGDownload link
is-ISDownload link
it-ITDownload link
ja-JPDownload link
ka-GEDownload link
kk-KZDownload link
km-KHDownload link
kn-INDownload link
kok-INDownload link
ko-KRDownload link
ku-Arab-IQDownload link
ky-KGDownload link
lb-LUDownload link
lo-LADownload link
lt-LTDownload link
lv-LVDownload link
mi-NZDownload link
mk-MKDownload link
ml-INDownload link
mn-MNDownload link
mr-INDownload link
ms-MYDownload link
mt-MTDownload link
nb-NODownload link
ne-NPDownload link
nl-NLDownload link
nn-NODownload link
nso-ZADownload link
or-INDownload link
pa-Arab-PKDownload link
pa-INDownload link
pl-PLDownload link
prs-AFDownload link
pt-BRDownload link
pt-PTDownload link
quc-Latn-GTDownload link
quz-PEDownload link
ro-RODownload link
ru-RUDownload link
rw-RWDownload link
sd-ArDownload link
si-LKDownload link
sk-SKDownload link
sl-SIDownload link
sq-ALDownload link
sr-Cyrl-BADownload link
sr-Cyrl-RSDownload link
sr-Latn-RSDownload link
sv-SEDownload link
sw-KEDownload link
ta-INDownload link
te-INDownload link
tg-Cyrl-TJDownload link
th-THDownload link
ti-ETDownload link
tk-TMDownload link
tn-ZADownload link
tr-TRDownload link
tt-RUDownload link
ug-CNDownload link
uk-UADownload link
ur-PKDownload link
uz-Latn-UZDownload link
vi-VNDownload link
wo-SNDownload link
xh-ZADownload link
yo-NGDownload link
zh-CNDownload link
zh-TWDownload link
zu-ZADownload link

Windows 10 build 15063 32-bit MUI/LPs

af-ZADownload link
am-ETDownload link
ar-SADownload link
as-INDownload link
az-Latn-AZDownload link
be-BYDownload link
bg-BGDownload link
bn-BDDownload link
bn-INDownload link
bs-Latn-BADownload link
ca-ESDownload link
ca-ES-valenciaDownload link
chr-CHER-USDownload link
cs-CZDownload link
cy-GBDownload link
da-DKDownload link
de-DEDownload link
el-GRDownload link
en-GBDownload link
en-USDownload link
es-ESDownload link
es-MXDownload link
et-EEDownload link
eu-ESDownload link
fa-IRDownload link
fi-FIDownload link
fil-PHDownload link
fr-CADownload link
fr-FRDownload link
ga-IEDownload link
gd-GBDownload link
gl-ESDownload link
gu-INDownload link
ha-Latn-NGDownload link
he-ILDownload link
hi-INDownload link
hr-HRDownload link
hu-HUDownload link
hy-AMDownload link
id-IDDownload link
ig-NGDownload link
is-ISDownload link
it-ITDownload link
ja-JPDownload link
ka-GEDownload link
kk-KZDownload link
km-KHDownload link
kn-INDownload link
kok-INDownload link
ko-KRDownload link
ku-Arab-IQDownload link
ky-KGDownload link
lb-LUDownload link
lo-LADownload link
lt-LTDownload link
lv-LVDownload link
mi-NZDownload link
mk-MKDownload link
ml-INDownload link
mn-MNDownload link
mr-INDownload link
ms-MYDownload link
mt-MTDownload link
nb-NODownload link
ne-NPDownload link
nl-NLDownload link
nn-NODownload link
nso-ZADownload link
or-INDownload link
pa-Arab-PKDownload link
pa-INDownload link
pl-PLDownload link
prs-AFDownload link
pt-BRDownload link
pt-PTDownload link
quc-Latn-GTDownload link
quz-PEDownload link
ro-RODownload link
ru-RUDownload link
rw-RWDownload link
sd-ArDownload link
si-LKDownload link
sk-SKDownload link
sl-SIDownload link
sq-ALDownload link
sr-Cyrl-BADownload link
sr-Cyrl-RSDownload link
sr-Latn-RSDownload link
sv-SEDownload link
sw-KEDownload link
ta-INDownload link
te-INDownload link
tg-Cyrl-TJDownload link
th-THDownload link
ti-ETDownload link
tk-TMDownload link
tn-ZADownload link
tr-TRDownload link
tt-RUDownload link
ug-CNDownload link
uk-UADownload link
ur-PKDownload link
uz-Latn-UZDownload link
vi-VNDownload link
wo-SNDownload link
xh-ZADownload link
yo-NGDownload link
zh-CNDownload link
zh-TWDownload link
zu-ZADownload link

After you get correct language pack run the following commands in cmd as administrator:

1. Install new language pack:

dism /Online /Add-Package /PackagePath:C:\lp.cab

(assume that language pack is copied to c:\ drive and has name lp.cab)

2. List installed language packs:

dism /Online /Get-Packages

3. Copy identifier of previous language pack which came with the system and remove it:

dism /Online /Remove-Package /PackageName:{package_id}

Where instead of {package_id} you should use copied language pack identifier. After that restart Windows (it will ask you to do that) and after some time when it will be restarted OS UI will use new language.

Monday, January 29, 2018

Internal details of setting Access request settings for Sharepoint sites

In Sharepoint you may configure Access request settings which will allow users to ask for access to the site from appropriate responsible person (I intentially don’t tell site owner here because recipient of access requests may be different person – see below). In order to do it first of all you need to configure outgoing email settings in Central administration. Access request settings for web site may be configured from Site settings > Site permissions > Access request settings:

When you click this link Sharepoint loads setrqacc.aspx into modal window. If we will check its codebehind class Microsoft.SharePoint.ApplicationPages.SetRequestAccess we will see that it first checks SPWebApplication.RequestAccessEnabled internal property:

In this property it checks another property IsEmailServerSet:

which in turn checks outgoing email server address:

That’s why setting outgoing email settings should be done before to configure Access request settings.

Now let’s check SetRequestAccess.OnLoad method again and see how “Allow access requests” checkbox is initialized with checked/unckecked values. As it is shown above OnLoad method calls ToggleSelectedAndSetObjectType method:

and the last one checks “Allow access requests” checkbox when SPWeb.RequestAccessEnabled property set to true. Let’s check code of SPWeb.RequestAccessEnabled property:

I.e. it is readonly property which returns true only when Access request email is set, i.e. other property SPWeb.RequestAccessEmail. So basically in order to enable Access requests you need to set SPWeb.RequestAccessEmail for your web site.

The problem however is that Sharepoint for some sites sets SPWeb.RequestAccessEmail to default value someone@example.com – see first picture in this post. There is internal method SPWeb.EnableAccessRequestsIfNeeded where it is done:

Here it’s code as it doesn’t fit to post column’s width:

   1:  
   2: private void EnableAccessRequestsIfNeeded()
   3: {
   4:     if (string.IsNullOrWhiteSpace(this.RequestAccessEmail) &&
   5:         this.HasUniqueRoleAssignments && this.WebTemplateId != 3
   6:         && this.WebTemplateId != 16 && this.WebTemplateId != 17 &&
   7:         this.WebTemplateId != 18 && this.Site.WebApplication.RequestAccessEnabled)
   8:     {
   9:         this.RequestAccessEmail = "someone@example.com";
  10:     }
  11: }

So for root webs of new site collections which have HasUniqueRoleAssignments = true and which use web template id except 3, 16, 17, 18 (and of course if SPWebApplication.RequestAccessEnabled is set to true – see above) Sharepoint will set SPWeb.RequestAccessEmail to default value someone@example.com which will mean that Access requests will be enabled there by default. In the following forum post Access Requests recipient by Default it is mentioned that in this case, i.e. when default email address someone@example.com is used, SPWeb.Site.Owner.Email will be used for sending access requests, although I didn’t check it by myself.

And one more thing: if you are working with CSOM note that Web.RequestAccessEmail property is available there only starting with version 16.1.4727.1200 onwards. Basically it means that it is not possible to change Access requests settings by CSOM for Sharepoint 2013 on-premise. It is still possible though via basic server object model.