Removing External Users fully from a SharePoint Tenancy using PowerShell


This blog post has all come about as the client I was working for was having problems sharing documents in SharePoint with some external users. It turned out that the user was already in Azure AD as a Contact which is part of Exchange. This meant when an internal person attempted to share/Invite into SharePoint/MSTeams it all appeared to work correctly for the external user, but sometimes it didn’t. When looking at external users through the Admin portal, this external user was showing, but their email address was blank. After speaking with Microsoft, it turns out, because the email address was already found within the tenancy, it creates a unique violation when adding the external user to the Active Directory.

I have been working with Microsoft support regarding this, and the resolution was that this is as design!!??! Only by feeding back on the Office 365 uservoice this issue “might” looked at and fixed. See resolution notes below:

Symptom:
When you invite external users who exist as contacts in your environment, their email does not get populated in their guest user ID which results in them not being able to login to your environment and access the shared data.
Cause:
The issue is coming from a conflict caused by the email address which is already populated for the mail contact.
Resolution:
This is behavior by design as all objects in Azure AD have to be unique.
You cannot have 2 objects with the same email address.

When you invite one of your contacts to your content in O365, it actually creates a completely new guest user object in your environment and since the email address which is supposed to be populated in the email attribute is already in use by the contact, the email address does not get populated.

The only way to resolve this issue at the moment is to eliminate any conflicts that are in place, by removing the conflicting email contact and re-invite the user to your content.
More information:
The best thing I can offer to you is the following:

Please go to our UserVoice portal where other people are facing the same behavior and up-vote it, comment and have the whole IT department do the same as well.

Allow a “Guest User” to be converted to a different account type
https://office365.uservoice.com/forums/273493-office-365-admin/suggestions/19966537-allow-a-guest-user-to-be-converted-to-a-differen

This led me to working on a process and script that would remove the users from everywhere.

Locations to remove the External User from:

  • Contacts
  • Azure AD Guest Users
  • Azure AD Deleted Users
  • All SharePoint Sites
  • All SharePoint Hidden User lists
  • SharePoint User Profile

Contacts

To remove the External User from the contacts you will need to use the MSOL PowerShell module.

Or you can manually do this by going to admin.microsoft.com and under Users -> Contacts select the user and click Delete contacts.

Azure AD

To remove the External User from Azure AD you will still require using the MSOL PowerShell module. In fact, this script and the above script could be merged.

To do this manually, in admin.microsoft.com under Users -> Guest Users, select the user and click delete.

Then go into Users -> Deleted users and remove them from there.

Remove from SharePoint

To remove from SharePoint, if you have a large tenancy and you don’t know all the places where the external user could have been shared with, then you will have to use the following script. This script will remove the external user from the SharePoint Site, ensure that they are removed from the User Information list, and then lastly it will clear the person from the SharePoint User Profile.

I discovered that if I didn’t remove them from the User Profile, when attempted to reshare a document with that user, the people picker would grab the internal userprincipalname (<ExternalUserEmail>#EXT#@<Tenant>.onmicrosoft.com) as the email address and then prevent me clicking the Sharing button. This is because the people picker uses Graph API /Me/People and grabs the value from there. Once removed from everywhere, including the User Profile this no longer happens.

The following script uses SPO PowerShell Module and you will need to connect first using Connect-SPOService. The account that you use, needs to be a SharePoint Global Administrator.

The script checks if it can find the ExternalUser, and if it can remove the user using Remove-SPOExternalUser.

Then it loops through every site collection and looks for the user using Get-SPOUser with the internal userprincipalname. If found it removes the user using Remove-SPOUser. Once it has looped through all SharePoint sites, it then checks the SharePoint User Profile and removes the user from UserProfile Remove-SPOUserProfile. This command will remove a user from the UserProfile if they in the “Active Profiles” or the “Profiles Missing from Import”

If the plan is to add the external person back into your tenant, once the script has run, you will need to wait at least a few hours (maybe leave it for a day to be sure) to ensure all back end processes of Microsoft have completed.

When you share a document/folder with the external user they will get the invited link and enter a code experience, this way they do not turn up inside you Azure AD. However, if you share a site with them, or add them to a MS Teams, they will appear in your Azure AD correctly.

SharePoint Online site keeps refreshing on Microsoft Edge


A weird thing started happening on my Microsoft SharePoint site yesterday. Every time I hit a page/site the browser continuously refreshed over and over. I had no problems if I did this in Chrome, or even Internet Explorer, only in Microsoft Edge. Originally I thought it was a problem with the site, because if I went to a different tenant I didn’t encounter the same issue.

So, I then booted up another pc that had Microsoft Edge on it, and viewed my site expecting it to refresh over and over, but it didn’t. Therefore, it seemed to be a problem with my Edge explorer.

I first tried to clear all settings. Clicking on the 3 ellipses and then select Settings > Clear browsing data. I ticked everything to be cleared, and then clicked Clear. Unfortunately, this didn’t work for me, and made my Edge explorer crash on opening.

Extreme measures were required. I needed to complete reset Microsoft Edge. Now if you are following these steps, I must warn you, you might lose your favourites, history and settings. I recommend that you create a full backup or create a system restore point. Although I didn’t bother with a system restore or backup. My favourites were still there afterwards, I’m not sure if this is a syncing thing between my devices though, as I’ve just noticed that my favourites are the same on two machines.

Fixing Microsoft Edge

  • Close all open Microsoft Edge browsers.
  • Open file explorer and navigate to the following location (You will need to show hidden files):
    • C:\users\<YourUserName>\AppData\Local\Packages
  • The folder called Microsoft.MicrosoftEdge_8wekyb3d8bbwe first right click it and select Properties, and remove the check from the Read-Only option, click Apply and then OK
  • Now try and delete the folder. If you get Access Denied prompts, just select Continue. Any files you cannot delete just skip for now.
  • Go inside the Microsoft.MicrosoftEdge_8wekyb3d8bbwe folder and try deleting any remaining folders, you might be not able to delete the AC folder.
  • Restart your computer.
  • Now we are going to obtain the Microsoft Edge package and reregister it. Open Windows PowerShell by right clicking it
    and Run as administrator
  • Type the following to get to your user cd c:\Users\<YourUserName> press enter.
  • Now type
Get-AppXPackage -AllUsers -Name Microsoft.MicrosoftEdge | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppXManifest.xml" -Verbose}
  • If successfully you will not see any red message in the powershell window, just a yellow one saying operation complete for C:\Windows\SystemApps\Micorosft.MirosoftEdge.8wekyb3d8bbwe\AppXManifest.xml”
  • Reboot your machine on more time. Afterwards Microsoft Edge will be restored and I found that I didn’t have a refreshing issue with my SharePoint online site anymore.

Using HTML template and JQuery.clone()



In a recent proof of concept I was putting together, I needed a way to display different data on the fly, but with the same layout. As it was for a SharePoint 2013 SharePoint hosted app, all I had was Java Script and HTML to perform my task. Originally when I first started to write the code I was building the DIV, SPAN and other objects in the code, line by line. When I was about to start the code for the 3rd section, I thought there must be an easier way.

And there is! Create a template on the HTML page to clone when needed, but have it hidden.

Below is an image of the template if it was visible on the page.


If we take a look at the HTML for it, you will see it just a simple HTML layout. The div with the ID “details” is where we will be cloning the template to in code. (Please note I’m not a designer and there are probably huge mistakes with this. Happy to be corrected).

</pre>
<div id="details"></div>
<div class="hiddenTemplate">
<div id="Template">
<h2><label class="header">This is Header</label></h2>
<div class="buttonDiv"><button class="deleteButton" type="button">Delete</button></div>
<b>ID: </b><span class="itemId">ID</span>

<b>Name: </b><span class="&quot;itemName">Name</span>

<span class="descriptionArea">
<b>Description: </b><span class="itemDescription">Description</span>

</span>
<span class="parentArea">
<b>Parent ID: </b><span class="itemParentID">Parent ID</span>

<b>Parent Name: </b><span class="&quot;itemParentName">Parent Name</span>

</span>
<div class="addItem"><span class="addItemInstruction">Add Items here</span>

<input class="addItemInput" type="text" />
<span class="addButtonSpan"><button class="addButton" type="button">Add</button></span></div>
</div>
</div>
<pre>

Using css I’m able to style and hide the template from the user on the page.

.hiddenTemplate {
display:none;
}
.deleteButton
{
margin-left:0px;
margin-top:2px;
}
.addItem
{
border:1px dotted lightgray
}
.parentArea
{
.display:none;
}

In your JavaScript code, once you have obtained your data you simply grab a copy of the template, clone it using $(‘#Template’).clone(); and then update any details before adding it to the “details” div.

I needed to use the template 4 times and show it slight different for each one, and also depending on values returned. Below is the code for just one of my returned items, however you can see how simple in a loop you could reuse this, or design it differently depending on the data coming back.

 //TermDetails.
 //Get if it is the root term, Term Name and Description.
 var root = termDetails.get_isRoot();
 var termName = termDetails.get_name();
 var termDescription = termDetails.get_description();

//Find template and Clone it.
 var termTemplate = $('#Template').clone();
 //Get the header, and update the text within the clone template.
 termTemplate.find('.header').text("Term Details");
 //Give the template a unique ID, so it can be referenced later in code if needed.
 termTemplate.attr('id', "boxDetails" + termId.toString());

//Show the display button, and give it an on click function.
 termTemplate.find('.deleteButton').show().on('click', function () { removeTerm(termStoreId, groupId, termSetId, termId) });
 //Display Item ID.
 termTemplate.find('.itemId').text(termId.toString());
 //Display Item Name.
 termTemplate.find('.itemName').text(termName);
 //If there is no description don't show it
 if (termDescription == "")
 termTemplate.find('descriptionArea').hide();
 else
 termTemplate.find('itemDescription').text(termDescription.toString());

//If not root, then it has a parent, find out who the parent is.
 var parent;
 if (!root) {
 parent = termDetails.get_parent();
 context.load(parent);
 context.executeQueryAsync(
 function () {
 //success getting parent
 var parentName = parent.get_name();
 var parentId = parent.get_id();
 //Show Parent Area.
 termTemplate.find('.parentArea').show();
 //Display Parent ID.
 termTemplate.find('.itemParentID').text(parentId.toString());
 //Display Parent Name.
 termTemplate.find('.itemParentName').text(parentName);
 }, function () {
 //failed
 console.log("Error obtaining parent details");
 });
 }

//Put instructions for the add button
 termTemplate.find('.addItemInstructions').text("Please enter the name of the Term to add to " + termName);
 //Give input box an ID to reference later
 termTemplate.find('.addItemInput').attr('id', 'createSubTermTextBox');
 //Give button an ID.
 termTemplate.find('.addButton').attr('id', 'createSubTermButton');
 //Change text of button.
 termTemplate.find('.addButton').attr('value', 'Add Term');
 //Give the button an on click function
 termTemplate.find('.addButton').on('click', function () { addSubTerm(termStoreId, groupId, termSetId, termId); });

//Add the template to the details tag.
 $('#details').append(termTemplate);
 

With some additional css the final outcome looks like below.
 

Battlefield 3 and punkbuster


So tonight, the first time of playing Battlefield 3 on my Windows 8 machine. I was getting punkbusted within 3 minutes of playing a game. I tried updating punkbuster using evenbalance.com that didn’t work. I tried disabling Avast, that didn’t work. I even went into the game install folder and tried pbsvc.exe which reinstalled punkbuster again for me. This didn’t work but it did give me a clue.

Apparently pnkBstrB service wasn’t communicating properly. So I checked in my services.msc and PnkBstrB service was not running. As soon as I started the service again I had no Punkbuster problems.

I hope this helps someone else.

How to post script/code on word press


Just use the sourcecode tag in the following way (don’t put the underscores in):

[__sourcecode lanaguage=”csharp”]
string s = “hello world”;
System.Console.WriteLine(s);
[__/sourcecode]

And it will display like this.


string s= "hello world";
System.Console.WriteLine(s);

The following languages are support.

  • actionscript3
  • bash
  • clojure
  • coldfusion
  • cpp
  • csharp
  • css
  • delphi
  • erlang
  • fsharp
  • diff
  • groovy
  • html
  • javascript
  • java
  • javafx
  • matlab (keywords only)
  • objc
  • perl
  • php
  • text
  • powershell
  • python
  • r
  • ruby
  • scala
  • sql
  • vb
  • xml

 

First blog post


How hard is it to write a good opening line? Been staring at this for a while now.

Ok, my name is Paul Matthews. I’m a father of one beautiful daughter, and live with my partner. We have no pets, and live in a 3 bed house. I’m a PC gamer and my favourite game is Battlefield 3. My gaming tag is Cann0nF0dder (the o’s are zeros) which is partly why my wordpress domain name is cann0nf0dder. Please feel free to add me as a friend.

Why am I now writing a blog?
Within our company we have a department blog which I am a main contributor too. It is a valuable source of information from something simple to something technical, but it’s not accessible from outside of the company firewall. It is such a useful tool, I decided to create my own one.

First technical post coming soon….