Wednesday, October 18, 2023

Disabled or inactive User Accounts in M365 - a prerequisite for Governance

 

Among the many "triggers" in SharePoint Online these are the ones with the greatest impact:

Content created

Content updated

User Account disabled


Today we are taking a look at the latter and how to detect that a User Account no longer is active, as this is the primary trigger for a lot of governance actions.


You might expect that a key property of a User Account such as if it is active should be pretty obvious, but it is NOT. (unless HR remembers to inform you) 


The fellowing approaches to detecting disabled or inactive user account is available as a PnP Script Samples PowerShell script 😊


First of all, what do we mean by "User Account"? 

Are we defining this as a User Profile in the SharePoint User Profile Application (UPA)?

Are we talking about the Users endpoint in Microsoft Graph?


The answer is YES, as both can be used to decide if the User Account is active.


HideFromAddressLists

In the UPA we have the SPS-HideFromAddressLists property which, as the name implies, is used to hide the account from a address list. This property is synchronized into the UPA and originates in Exchange and is used to exclude the account from the Global Address List (GAL).

Once the SPS-HideFromAddressLists property changes to true, it is very often a good indicator that the User Account has been disabled. 

In Microsoft Search this property is the only way to exclude accounts from being shown.


Customer AD Properties

Another option is that the organization have added a customer User Profile Property like "EmployeeStatus"/"UserDisabled" and this property is sync'ed from AD/AAD to SharePoint via a customer sync job. 

I have even seen an organization where the UserAccountName was prefixed with ZZ[YearOfLeaving] as a way to tag the user as disabled 😮

Please consult your friendly Entra ID admin for details if the organization is using this approach.


Graph 😃

Using Graph we can query the Users endpoint to get the accountEnabled property



However, you will have to check with the Entra ID team in your organization to verify when Accounts gets disabled and if it is a good indicator that the employee no longer is with the Organization. 

Perhaps you will have to check if another criterium is fulfilled, like no licenses assigned, in order to determine that the user has been off-boarded.


No Activity

Yet another way to query if the user is inactive is to use the Auditlogs in Graph to check if the user has been active within the last X days.

https://graph.microsoft.com/v1.0/auditLogs/signIns?$top=1&$filter=userPrincipalName eq 'LeeG@tcwlv.onmicrosoft.com'


 The absence of logins is not a bullet proof way to detect that the user no longer is with the Organization, but it is a pretty good indicator.

Summary

The above should make it clear that we most likely will have to test a number of criteria in order to determine if we should start the governance actions, like rerouting the workflows assigned to the user in question, reassign ownerships on assets and so on.

The list of criteria will probably vary from organization to organization, but using the right mix should give you a good chance to detect those users.


#SharingIsCaring

Monday, October 9, 2023

New video on YouTube - Options for integrating 3rd party data with SP/MS Search as of Q4 2023

 


5 October I gave a presentation at the Danish SharePoint User Group's meeting, covering the options that we have as of Q4 2023 when we want to integrate 3rd party data into Microsoft or SharePoint Search.

This is a shortened version and in English rather than Danish.  ;-) 



View from MAN Energy (the host for the meeting, and thanks for that) is magnificent on a beautiful evening. 


Tuesday, September 12, 2023

How SharePoint handles DateTime stamps - and why it causes PnP Search Filter URLs to be offset

 

TimeZones and how SharePoint handles timestamps

We recently had an issue logged in the PnP Modern Search repository regarding why the date filter values were "wrong".

Actually, the date filter values were correct as they were offset to match the current users Time Zone, which can be pretty hard to understand, hence this summery:


Disclamer: I am in Denmark, currently in UTC+2 (summertime)

Each time a timestamp is saved in SharePoint it is saved in UTC0, so when I upload a document at 13.45 local time the timestamp saved in the backend will read 11.45.


But in the Document library it clearly shows 13.45, what gives?

Each time the SharePoint Front End displays a timestamp it is offset to the local timezone, so my colleague in Los Angeles looking at the same document will see the timestamp as 04.45 (9 hours behind my timezone).


However, when you get the timestamp from an API you get the time in UTC0 !!!!

So, if I am looking for documents that was created between 4 April and 10 April I must answer the question: from which timezone do I define when 4 April begins?

If I lived in New Zealand(UTC+12) 4 April starts a lot earlier than it does here in Denmark(UTC+2).

So, when somebody asks you to set up a PowerShell script to pull all Contracts (content type) created on 10 April, you have to ask is the offset should be based on the Time Zone of the requestor or something else.


In PnP Modern Search any time-based query is automatically offset to match the Time Zone of the current user. 
When I query for something created between 4 April and 10 April the query is changed to:
creased between 3 April 22h00m and 9 April 22h00m.
The reason is that when 4 April started in Denmark the time was 3 April 22h00m UTC0 which is the values in the backend.

You can find your current TimeZone info by navigating to a classic SP Page, hitting F12, go to the Console tab, enter _spPageContextInfo and hit Enter, look for webTimeZoneData:




Time Zones might be hard to deal with, but I guess it is there to stay :-)


Thursday, August 17, 2023

PnP Modern Search Office Hours - Online tips and advise

 

For some time, I have assisted the PnP Modern Search project by answering questions and handling bug reports.

However written communication is often not optimal to describe the issue you are facing which has been a blocking bug for several weeks now. And if you are new to SharePoint/Microsoft Search there are a substantial number of pitfalls that can prevent your progress.





Therefore, I am now offering:

PnP Modern Search Office Hours - Online tips and advise 

Every 2nd week I will offer 4 slots of 15 minutes each (for free of cause) where I will set up a Teams Meeting with each participant. The sessions will be 1:1 and will not be recorded, and using screen sharing we should be able to pinpoint the issue and get you going again. 


So, if you are stuck on a Search related issue or need some advice regarding Search feel free to sign up  in this MS Form



The first session is August 22 @16.00 UTC+1



PnP Modern search: Set refiners in the URL

 

The PnP Modern Search Filter web part supports deep URL linking, so if you provide the correct URL both the Search text and selected filters can be activated once the page loads:









However, getting the correct URL can be tricky. The recommended approach is to set the search text and the relevant Filters and then grab the browser URL. It will look like this:


https://[Tenant].sharepoint.com/sites/PnPModernSearch/SitePages/Filters-read-from-url-are-not-set-in-the-filter-web-part--3157.aspx?q=cmo&f=%5B%7B%22filterName%22%3A%22RefinableString09%22%2C%22values%22%3A%5B%7B%22name%22%3A%22CMO%22%2C%22value%22%3A%22%C7%82%C7%82434d4f%22%2C%22operator%22%3A0%2C%22disabled%22%3Afalse%7D%5D%2C%22operator%22%3A%22or%22%7D%5D#

The components are: the URL to the page + the q section + the f section.

The q section is just the text to be applied in the Search Box. 

The f section is the Filter settings. It looks pretty messy, but this PowerShell snippet will clean it up:


$url = Read-Host "Enter URL"

$pageURL = $url.Substring(0, $url.IndexOf("?"))

$filterURL = $url.Substring($url.IndexOf("?")+1)

$filterURL = $filterURL.Replace("%5B", "[")

$filterURL = $filterURL.Replace("%7B", "{")

$filterURL = $filterURL.Replace("%22", '"')

$filterURL = $filterURL.Replace("%3A", ":")

$filterURL = $filterURL.Replace("%2C", ",")

$filterURL = $filterURL.Replace("%5D", "]")

$filterURL = $filterURL.Replace("%7D", "}")

$filterURL = $filterURL.Replace("%C7%82%C7%82", "Ç‚Ç‚")


$newUrl = $pageURL + "?" + $filterURL

$newUrl


And once cleaned up it looks like this:

f=[{"filterName":"RefinableString09","values":[{"name":"CMO","value":"Ç‚Ç‚434d4f","operator":0,"disabled":false}],"operator":"or"}]


The value tag above is the Filter Token, and you will be able to find those using the SP Search Query Tool.












Tuesday, June 6, 2023

Searching for phone numbers in the Employee Directory

 

Phone numbers search - where the answer is "It depends"


A question I get very often when assisting customers with People Search is why they can't search for phone numbers.


The answer is that they in most cases CAN search for phone numbers. However, it might not be as straightforward as they would like.

The primary reason searching for phone numbers is difficult is that we as humans prefer that phone numbers are displayed in a form that is easy to read and remember.

For instance, a US phone number will often be displayed as +1 980 555 0101, and those spaces really can mess up the search. More on this later.


In some cases, the KQL has been twisted in such a way that you can't search for phone numbers:


I searched for Joni, and copied her phone number.


But I get no results?


In this case the query is set up to search for FirstName only: FirstName:{subjectTerms}


I then update the query to: FirstName:{subjectTerms}  OR MobilePhone:{subjectTerms}

But still no results, what gives?


Checking the UPA reveals that Joni doesn't have a Mobile nor Home phone number.



OK, so the query should be like this perhaps: 

FirstName:{subjectTerms}  OR WorkPhone:{subjectTerms} 

Using the query text +1 980 555 0101 it returns no hits.

Looking at the query sent to the API reveals that it is transformed into 

FirstName:+1 980 555 0101  OR WorkPhone:+1 980 555 0101

 

Ouch, not a well-formed query.


The final query will in this case look like this:

(FirstName:({subjectTerms}) OR WorkPhone:({subjectTerms})*  )

and the resulting query in the API call like this:

(FirstName:(+1 980 555 0101) OR WorkPhone:(+1 980 555 0101)*  )


Another option is to abandon the specific search and use a more generic query: {subjectTerms}*  

(However, this might find matches in unexpected properties)


Query text                            resulting query                Hits

joni                                        joni*                                1

+1 980 555 0101                +1 980 555 0101*            1

+1 980                                +1 980*                            1

+1 9                                    +1 9*                                6

0101                                    0101*                               0


Sorry, SharePoint search can only handle "starts with" wildcards, not "ends with"


Yet another approach is to use the phone number properties for display purposes only, and create your own User Profile properties, like "WorkPhoneAsText" which contains the WorkPhone value stripped for spaces and prefixes. 

I have primarily used it with customers that used the last 4 digits like a local extension, and would therefore have a "LocalExtension" property in the UPA and a matching managed Property.