Thursday, April 2, 2020

Problem with fetching photos of private O365 groups

As you probably know in O365 we may create groups which has one of the following visibilities:

  • Public
  • Private

Everybody in your organization may join/leave public groups while for private groups only owners of this group may add you to the group. In this article I will describe one problem related with fetching private group photos via Graph (see also my previous article where I mentioned another problem related with groups images: Why you should be careful with /groups/{id}/photo and /users/{id}/photo endpoints in MS Graph or unintentional getting photos of big sizes in Graph).

In order to fetch group photo the following Graph endpoint should be used:

https://graph.microsoft.com/v1.0/groups/{id}/photo

First of all we need to mention that this endpoint is available only via user delegated permissions (it doesn’t work with app-only permissions). If we will try to fetch photo of some group in Graph explorer using service account which is not member of this group we will get 404 Not found error:

After we will add the same service account to members of the same group image will be retrieved successfully:

Even if we will try to fetch photos of private group under global admin account which is not member of this group – we will still get 404 Not found. So the only way to fetch photo of the private group is to add user account to members or owners of this group. Be aware of this problem when will plan groups images fetching functionality.

Wednesday, April 1, 2020

One problem with AllowToAddGuests and AllowGuestsToAccessGroups O365 groups tenant settings

As you probably know AllowToAddGuests and AllowGuestsToAccessGroups tenant settings determine whether or not external users are able to access O365 groups in your tenant. You may view or change them via PowerShell:

AzureADPreview\Connect-AzureAD
Get-AzureADDirectorySetting

Which will show something like that:

If you will try to update them then you may face with different behavior on different tenants. E.g. if we will try to change them to true like this:

$groupsConfig = Get-AzureADDirectorySetting -Id {settingId}
$groupsConfig["AllowToAddGuests"] = $true
$groupsConfig["AllowGuestsToAccessGroups"] = $true
Set-AzureADDirectorySetting -Id {settingId} -DirectorySetting $groupsConfig

we may get the following result:

Note that in script we used lowercase $true while in result we got True with capital T. But if you will try to run the same script on another tenant it may save values using the same letters registry as used in script. I.e. $true will be saved as true and $True will be saved as True. And if software uses case-sensitive comparison it will cause problems. So be aware of this problem – hope it will be help someone.

Wednesday, March 18, 2020

How to provision modern Sharepoint page with custom SPFx web part via PnP template

If you developed custom SPFx web part and tested it on dev env you will most probably want to automate it’s provisioning to customers environments. In this article I will show how to do that via PnP template.

When you need to create PnP template the simplest option is to export it form existing site and copy those components which you need. This is much faster than trying to remember whole PnP schema.

So first of all we need to build sppkg in release mode (with “--ship” parameter) and upload it to App catalog:

gulp clean
gulp bundle --ship
gulp package-solution --ship

After that add your web part to some modern page – we will use test.aspx for example here. Now you have modern site running with your custom web part.

Next step is to export PnP template from your site:

Get-PnPProvisioningTemplate -Out template.xml

When export will be done edit template.xml and copy section pnp:ClientSidePages which may look like this (of course if you have several SPFx web parts there will be several pnp:CanvasControl instances):

<pnp:ClientSidePages>
 <pnp:ClientSidePage PageName="test.aspx" EnableComments="false" Publish="true">
   <pnp:Sections>
  <pnp:Section>
    <pnp:Controls>
   <pnp:CanvasControl WebPartType="Custom" JsonControlData="..." ControlId="..." Order="1" Column="1" />
    </pnp:Controls>
  </pnp:Section>
   </pnp:Sections>
 </pnp:ClientSidePage>
</pnp:ClientSidePages>

and paste it to PnP template which you are going to use for provisioning on customers environments. When this template will be applied to the site it will have modern page with your custom SPFx web part.

Monday, March 16, 2020

Move Mercurial repository to Git on Bitbucket

As you probably know Bitbucket will discontinue Mercurial support – all Mercurial repositories will be removed at June 1, 2020. So if you have Mercurial repositories there it is good to take care about them in advance and move them to Git which becomes basic source control in Bitbucket. Articles which I found had lack of some important information so it still took time to go through them. So I decided to write separate post and summarize all steps which are needed for moving Mercurial repositories to Git on Windows 10 PC:

1. Install latest version of TortoiseHG (older versions may not have HgGit plugin which will be needed below)
2. Install Git for Windows 3. Rename repository and make hg clone from new address
4. In HR repository folder enable HgGit plugin by adding following section to .hg/.hgrc file:
[extensions]
hggit=
5. Go to "C:\Program Files\Git\usr\bin" and run ssh-keygen.exe
Use default settings - it will add 2 files to C:\Users\{username}\.ssh: id_rsa and id_rsa.pub
6. Copy content of id_rsa.pub file
7. Login to bitbucket.org > Profile page > Settings > Security > SSH Keys > Add key > Insert content of copied id_rsa.pub
8. Go to hg repositoy folder:
8.1. in .hg/.hgrc add following line under [ui]:
ssh = "ssh.exe"
8.2. ensure that path "C:\Program Files\Git\usr\bin" (which is path to ssh.exe) is added to PATH environment variable 8.3 run the following command:
hg push git+ssh://{username}@bitbucket.org/{username}/{repository}.git
(git repository url can be copied from bitbucket and then replace https by git+ssh)

After that your repository should be available on Git with all version history and branches you had in Mercurial.

Thursday, March 12, 2020

Disable redirection from user details page UserDisp.aspx to MySite Person.aspx in Sharepoint

As you probably know Sharepoint has built in Created By and Modified By fields which are automatically added to all list items and documents which added to Sharepoint. Under the hood it is done by defining these fields in base content type derived by all other content types. If you add these fields to list views they will be clickable – it will be possible to click on user name and go to user details page UserDisp.aspx which will show user’s attributes (synced with AD if you use Windows authentication).

However in some cases you may notice that instead of UserDisp.aspx Sharepoint redirects to user profile page Person.aspx in MySites web application. If you are not planning to use user profiles and MySites in your Sharepoint site then this behavior may be unwanted and it may be needed to disable this automatic redirection.

Technically redirect is made by MySiteRedirection.ascx user control which is added by MySite farm scope feature (FeatureId = 69cc9662-d373-47fc-9449-f18d11ff732c) which has the following elements.xml file:

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Control Id="ProfileRedirection" Sequence="100" ControlSrc="~/_controltemplates/mysiteredirection.ascx"/>
    <Control Id="MobileSiteNavigationLink1" Sequence="100"
        ControlClass="Microsoft.SharePoint.Portal.MobileControls.MobileMySitePageNavigation"
        ControlAssembly="Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
    </Control>
</Elements>

In order to disable redirection we need to deactivate this feature:

Disable-SPFeature -Id 69cc9662-d373-47fc-9449-f18d11ff732c

After that after clicking user names in Created By/Modified By columns (and all other clickable columns of User or group type) user will be redirected to regular UserDisp.aspx page

Monday, February 17, 2020

Several mandatory steps for every SQL Server installation

Recently I’ve installed MS Sql Server (don’t remember how many times I’ve done that already :) ) on the new virtual machine and realized that I always perform the following steps after each Sql Server installation:

1. Disable “sa” login

This step was added to mandatory tasks list after sad story: in hte times of beginning of my IT carrier one of my Sql Server instances ran with enabled “sa” login. Password was not very strong and after some time it was brute forced and I got malicious Sql Server job which tried to download and execute remote code on my PC. Fortunately I noticed that in time and made necessary actions. After that I always disable built-in “sa” login on Sql Server – this is the first things hackers will try to brute force on your instance.

2. Configure backups

This step is quite obvious. Remember that you should not only test that your backups work by specified schedule (e.g. if you configured nightly backups – check after few days that backups are really made during last nights) but also test restore scenario. Some time these simple steps will save you from a lot of problems. You may configure backups in Sql Server Management Studio > Management > Maintenance plans > New maintenance plan > Add Back Up Database Task from toolbox: After configuring backup set schedule in the same window.

Another important note is that you should not store backups on the same PC. Safest option is to move backups from local PC to the cloud storage using some cloud backup tool.

3. Limit Sql Server trace log size

If Sql Server runs long period of time it may flood hard drive with trace logs (don’t mix it with transaction logs – these are different). By default they are stored in MSSQL/Logs subfolders under your Sql Server instance folder. In order to configure their size go to Sql Server Management studio > Management > Right click on SQL Server Logs > Configure. Set some value in “Limit the number of error log files before they are recycled” and/or “Maximum size for error log file in KB”:

This simple action will allow to keep Sql Server logs size controlled. If you have similar mandatory steps in your practice please share them in comments.

Tuesday, February 11, 2020

Resolve “Everyone except external users” group on old tenants

Some time ago I wrote how to get login name for special group “Everyone except external users” in Sharepoint Online: Get login name of special group “Everyone except external users” programmatically in Sharepoint. To remind: it is constructed like this:

"c:0-.f|rolemanager|spo-grid-all-users/” + tenantId

Today we faced with scenario when such login name could not be resolved on one customer’s tenant. After research it was found that similar issue was also reported on OfficeDevPnP github project page (see here). So as it turned out on old tenants this way of getting “Everyone except external users” may not work. As workaround you may use the following solution from OfficeDevPnP:

public override string GetEveryoneExceptExternalUsersName(Web web)
{
 string userIdentity = "";
 try
 {
  // New tenant
  userIdentity = $"c:0-.f|rolemanager|spo-grid-all-users/{web.GetAuthenticationRealm()}";
  var spReader = web.EnsureUser(userIdentity);
  web.Context.Load(spReader);
  web.Context.ExecuteQueryRetry();
 }
 catch (ServerException)
 {
  // Old tenants
  string claimName = web.GetEveryoneExceptExternalUsersClaimName();
  var claim = Utility.ResolvePrincipal(web.Context, web, claimName, PrincipalType.SecurityGroup, PrincipalSource.RoleProvider, null, false);
  web.Context.ExecuteQueryRetry();
  userIdentity = claim.Value.LoginName;
 }

 return userIdentity;
}

I.e. at first we try to get login name using "c:0-.f|rolemanager|spo-grid-all-users/” + tenantId and try to resolve group with this name. If it fails we call GetEveryoneExceptExternalUsersClaimName() extension method which returns localized name of “Everyone except external users” group for current tenant (it has translations of group name for all supported languages) and tries to resolve this special group using this name. This code will work both on new and old tenants.