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.

Updating an expired Client Secret of SharePoint Add-in


Been working with SharePoint Add-in tokens for a while now, but this week has been the first time I’m still working with an add-in longer than a year in one environment. My application start throwing the error message:

Error:Token request failed., InnerMessage:System.Net.WebException: The remote server returned an error: (401) Unauthorized.”

I knew that they did expire after a year, but never really thought about (until now) how to go about renewing them. Luckily Microsoft documentation nowadays is a lot better than it used to be. I found this walk through https://msdn.microsoft.com/en-us/library/office/dn726681.aspx. In case Microsoft takes the link down, or changes the URL, I will explain the steps below. I have also changed the code slightly for the Report on Client ID expiry dates, as the Microsoft one didn’t return the results I expected.

Report on Client ID expiry dates.

  • Open Windows Powershell and run the following cmdlet:
Connect-MsolService
  • A login prompt will appear, here enter the tenant-administrator credentials for the Office 365 tenancy where the add-in was registered with AppRegNew.aspx
  • You can generate a report that list each add-in in the tenant with the date that the secret expires with the following PowerShelll code.

    $applist = Get-MsolServicePrincipal -all  |Where-Object -FilterScript { ($_.DisplayName -notlike &quot;*Microsoft*&quot;) -and ($_.DisplayName -notlike &quot;autohost*&quot;) -and  ($_.ServicePrincipalNames -notlike &quot;*localhost*&quot;) }
    $output = &quot; &quot;
    foreach ($appentry in $applist)
    {
        $principalId = $appentry.AppPrincipalId
        $principalName = $appentry.DisplayName
    
        $results =  Get-MsolServicePrincipalCredential -AppPrincipalId $principalId -ReturnKeyValues $false | Where-Object { ($_.Type -ne &quot;Other&quot;) -and ($_.Type -ne &quot;Asymmetric&quot;) }
        if($results.count -gt 0)
        {
          $output += &quot;PrincipalId`t:`t$principalId`n&quot;
          $output += &quot;PrincipalName`t:`t$principalName`n&quot;
          $output += &quot;Keys`n&quot;
         foreach($result in $results)
         {
            $output += &quot;Type`t:`t&quot; + $result.Type + &quot;`n&quot;
            $output += &quot;Value`t:`t&quot; + $result.Value + &quot;`n&quot;
            $output += &quot;KeyId`t:`t&quot; + $result.KeyId + &quot;`n&quot;
            $output += &quot;StartDate`t:`t &quot; + $result.StartDate + &quot;`n&quot;
            $output += &quot;EndDate`t:`t&quot; + $result.EndDate + &quot;`n&quot;
            $output += &quot;Usage`t:`t&quot; + $result.Usage+ &quot;`n&quot;
            $output += &quot;`n&quot;
         }
         $output += &quot;-----------------------------------------------`n&quot;
        }
    }
    $output | Out-File &quot;c:\temp\appsec.txt&quot; 

     

    • The above code first filters out Microsoft’s own applications, add-ins still under development (and a now-deprecated type of add-in that was called autohosted).
    • Filters out non-SharePoint add-ins and add-in like workflow.
  • Open the file at c:\temp\appsec.text to see the report.

An example of the report below:

Note: The PrincipalID is your Client ID

Generate a new secret for another year.

To generate a new secret, you just need to run the following PowerShell script:

$clientId = &lt;#Replace with your ClientID of the add-in#&gt;
$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
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Verify -Value $newClientSecret
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Password -Usage Verify -Value $newClientSecret
$newClientSecret

The output of the above PowerShell file will give you a new Client Secret, take note of this:

Generate a new secret for 3 years.

It is possible to create a secret that will last 3 years, the PowerShell script is very similar to the above script, but now it has a start and end date.

$clientId = &lt;#Replace with your ClientID of the add-in#&gt;
$bytes = New-Object Byte[] 32
$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rand.GetBytes($bytes)
$rand.Dispose()
$newClientSecret = [System.Convert]::ToBase64String($bytes)
$dtStart = [System.DateTime]::Now
$dtEnd = $dtStart.AddYears(3)
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Sign -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Symmetric -Usage Verify -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd
New-MsolServicePrincipalCredential -AppPrincipalId $clientId -Type Password -Usage Verify -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd
$newClientSecret

Updating the remote web application using the new secret

  • If you are redeploying from Visual Studio you will need to update the client Secret in your Web.Config
  • If you are just updating directly in Azure, you can just go to the configurations and update the new secret there.

If you are using the default TokenHelper.cs file in your project, and it’s not the prerelease version then you can add a second app setting called SecondaryClientSecret. Here you would put in your old secret, and in the ClientSecret put in the new one. This is so if your token is going to expire soon, the application will still work as it will try one first then the other.

Time to propagate Client Secret to SharePoint

According the Microsoft link you should wait at least 24 hours to propagate the ClientSecret. However, I found as soon as I changed the secret I could use it straight away. After changing your Client Secret if you run the ‘Report on Client ID expiry dates’ powershell again, those dates didn’t update for me until the following day.

I ran the report the following day, and as you can see below, Demo App 1 which was shown in the screen shot above now has 3 new keys with new dates.

Deleting expired client secrets

As expired client secrets do not seem to get removed, it is recommended to delete them. The code below will grab any keys that are expired for a ClientID and delete them.

$clientId = &lt;#Replace with your ClientID of the add-in#&gt;
$keys = Get-MsolServicePrincipalCredential -AppPrincipalId $clientId -ReturnKeyValues $false
$dtNow = [System.DateTime]::Now
foreach($key in $keys)
{
 if($key.EndDate -lt  $dtNow)
 {
   write-host $key.KeyId &quot; Expired&quot;
   Remove-MsolServicePrincipalCredential -KeyIds @($key.KeyId) -AppPrincipalId $clientId
 }
}

Adding to Property Bag and Index using PowerShell in SharePoint Online


The other day, I very lazily, used SharePoint Designer to add a few items to the property bag. Soon as I done it, someone then said they wanted to search upon the values inside the property bag. Unfortunately this isn’t something that can be done via SharePoint Designer. After spending 5 minutes or so searching online for a pre-made PowerShell code, I could only find the solution for On-Prem.

The only piece of code I found to do this to SharePoint Online was through the Office Developer PNP CSOM code. I really didn’t want to create a Visual Studio project just to use the PNP Core. (See http://dev.office.com/patterns-and-practices for more information Office Dev PNP, there is so much information and cool videos there, there really is no need to re-hash anything in my own blog.) So I decided to just create PowerShell script which is pretty close to being like for like copy of the PNP Core method:

Web.AddIndexedPropertyBagKey(this Web web, string key)

Before I go into the code, I will explain how SharePoint knows which property bag items need to be indexed for search. Just by adding the item doesn’t make it visible to search, it has to be added to another property bag item called “vti_indexedpropertykeys“. The value of this property bag item is a pipe delimited Base64String. (Example: RABhAHUAZwBoAHQAZQByAA==|UwBvAG4A|cwBvAG4A| ). As you can see, it’s not just as simple as adding the string name to the vti_indexedpropertykeys value.

To encode a single value in PowerShell the following line of code works:

[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($value))

To decode a single value from Base64String to text the following PowerShell code works:

[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($EncodedValue))

The below code will ask you for the Web Url, your username, password (Secure string), propertybag key name and value. The code will first add or update the value in the property bag, and if it hasn’t been added to the index it will add it.

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")| Out-Null
$indexPropertyKeyConst = "vti_indexedpropertykeys"

function GetIndexedPropertyBagKeys($ctx)
{
  $results = @()
  $web = $ctx.Web;
  $ctx.Load($web.AllProperties)

  try
  {
  $ctx.ExecuteQuery();
  }
  catch{
   Write-host "Error accessing property bag " $_.Exception.Message -ForegroundColor Red
   exit 1
  }

  $indexPropertyBase64 = $web.AllProperties[$indexPropertyKeyConst];
  $separator = "|"
  $option = [System.StringSplitOptions]::RemoveEmptyEntries

  if(![string]::IsNullOrEmpty($indexPropertyBase64))
  {
    $resultsBase64 = $indexPropertyBase64.Split($separator, $option)

    foreach($r in $resultsBase64)
    {
     $results += [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($r))
    }
  }
    #comma not a mistake, required to ensure correct type is returned on empty or single array value.
    return ,$results
}

function GetEncodedValueForSearchIndexProperty($keysArray)
{
  $encode64Keys = [String]::Empty
  foreach($key in $keysArray)
  {
   $encode64Keys += [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($key))
   $encode64Keys += "|"
  }

  return $encode64Keys;
}

function AddIndexedPropertyBagKey($ctx, $propertyKey)
{
  [bool]$result = $false;
  [bool]$addValue = $false;
  $keys = GetIndexedPropertyBagKeys($ctx)

  if($keys -cnotcontains $propertyKey)
  {
     $addValue = $true;
  }

 if($addValue)
 {
   try
   {
   $keys += $propertyKey
   $keysBase64String = GetEncodedValueForSearchIndexProperty($keys)
   $ctx.Web.AllProperties[$indexPropertyKeyConst] = $keysBase64String
   $ctx.Web.Update()
   $ctx.ExecuteQuery()
   $result = $true
   }
   catch
   {
    Write-host "Error adding $propertyKey to index. " $_.Exception.Message -ForegroundColor Red
    exit 1
   }
 }

  return $result
}

function CreateUpdatePropertyBag($ctx, $propertyKey, $propValue)
{
  $web = $ctx.Web;
  $ctx.Load($web.AllProperties)
  try
  {
    $ctx.ExecuteQuery();
    $web.AllProperties[$propertyKey] = $propValue;
    $web.Update();
    $ctx.ExecuteQuery();
  }
  catch{
   Write-host "Error adding $propertyKey to property bag " $_.Exception.Message -ForegroundColor Red
   exit 1
  }
}

$webUrl = Read-Host -Prompt "Enter the WebUrl"
$username = Read-Host -Prompt "Enter your Email login"
$password = Read-Host -Prompt "Password for $username" -AsSecureString
$propKeyToIndex = Read-Host -Prompt "Enter the property key name to index"
$propValue = Read-Host -Prompt "Enter the $propKeyToIndex value"

$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)

CreateUpdatePropertyBag $ctx $propKeyToIndex $propValue;
AddIndexedPropertyBagKey $ctx $propKeyToIndex;

Write-Host "Complete" -ForegroundColor Green

You can download the source code directly from my OneDrive.

Thank you to my colleague Paul Perry with his help around PowerShell arrays.

PowerShell – Loop through Sites, Content Types, Lists.


Many times before I have had to write a bit of PowerShell to loop through a SharePoint site fixing something up. When I start writing this script I normally start from scratch each time. Basically I need to perform the same basic looping functionality, and then either at the List level, content type level maybe even at the item level I need to perform some change.

For example, I might need to delete a certain field from every list of type event, or I need to set the default value on a column, or perhaps you just want to loop through the site to see what is where. Whatever the reason is, if the change needs to be made in many places, I will need to loop through the environment.

The order typically I loop through would be:

  • Web
  • Content Type
  • Lists
  • Content Types in Lists
  • Fields in Content Types
  • Items

Depending on what I was trying to achieve, I would either add, or remove extra sections going forward.

The code below is the basic template I would use to get me going.

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")

#Functions
#Process the web Site, checking the Content Type at the web first before checking the Content Types at the list level.

function processWeb($web){
 #web
 $ctx.load($web)
 #Content Type
 $cts = $web.ContentTypes
 $ctx.load($cts)
 #Lists
 $lists = $web.Lists
 $ctx.Load($lists)
 $ctx.ExecuteQuery()
 #Display Web 
 write-host "Web Name:" $web.Title 
 processContentTypes($cts)
 processLists($lists)
}

#Loop through each content type
function processContentTypes($cts)
{
  foreach($ct in $cts)
  {
    Write-Host "Content Type:" $ct.Name             
    $ctx.Load($ct.FieldLinks);
    $ctx.ExecuteQuery();

    processFieldsLinks($ct.FieldLinks)
  }
}

#Loop through fields
function processFieldsLinks($fieldsLinks)
{
  foreach($f in $ct.FieldLinks)
  {
      Write-Host "Field " $f.Name 
  }
}

#Loop through each item
function processItems($items)
{
 foreach($item in $items)
 {
  if($item.FieldValues["Title"] -eq $null)
  {
   write-host "Item FileLeafRef: " $item.FieldValues["FileLeafRef"]
  }
  else
  {
   write-host "Item Title: " $item.FieldValues["Title"]
  }
 }
}

#Loop through each list in the site
function processLists($lists){
 foreach($list in $lists)
 {
  $ctx.load($list)
  $ctx.load($list.ContentTypes)
  $query = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(10000, 'UniqueId','ID','Created','Modified','FileLeafRef','Title')

  $listItems = $list.GetItems($query);
  $ctx.load($listItems)
  $ctx.ExecuteQuery();

  write-host "BaseTemplate: " $list.BaseTemplate " List: "$list.Title
  processContentTypes($list.ContentTypes)
  processItems($listItems)
 }
}

#Get the URL, and an account to log in with. (Needs permissions across the sites to make changes)
$webUrl = Read-Host -Prompt "Enter the URL of your Root site" 
$username = Read-Host -Prompt "Enter your Email login"
$password = Read-Host -Prompt "Password for $username" –AsSecureString

#Create Context and load sites.
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($webUrl)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)

$rootWeb = $ctx.Web
$childWebs = $rootWeb.Webs
$ctx.Load($rootWeb)
$ctx.Load($childWebs)
$ctx.ExecuteQuery();

#Process the rootweb
processWeb($rootWeb)

#process the child webs
foreach($childWeb in $childWebs)
{
 processWeb($childWeb);
}

#complete
Write-Host "******Completed*********" -ForegroundColor Green

Just to give an example of how I might adjust this, if I wanted to only loop through lists that are Site Pages, with BaseTemplate of 119, I would change the processLists() function to only process the list if the BaseTemplate is 119.

#Loop through each list in the site
function processLists($lists){
 foreach($list in $lists)
 {
   $ctx.load($list)
   $ctx.ExecuteQuery()

   if($list.BaseTemplate -eq 119)
   {
     $ctx.load($list.ContentTypes)
     $query = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(10000, 'UniqueId','ID','Created','Modified','FileLeafRef','Title')
     $listItems = $list.GetItems($query);
     $ctx.load($listItems)
     $ctx.ExecuteQuery();

     write-host "BaseTemplate: " $list.BaseTemplate " List: "$list.Title
     processContentTypes($list.ContentTypes)
     processItems($listItems)
   }
 }
}

Link to file in OneDrive: http://1drv.ms/1V5Q8j8

Handling Azure PowerShell with multiple Azure Subscriptions


The other day I was trying to create an Azure WebSite for a client, I ran the commands, everything was successful. However when I went to the client Azure tenant, there was no sign of the web site! But I could hit the URL, which proved the site existed. I was dreading creating a Microsoft Support call, but as it was near the end of the day I went home. Deal with it in the morning. That evening, I was doing some work at home, and logged into my own personal Azure Tenant, and there was the Website I created for the client.

It turns out,

It doesn’t matter if you are logged into your Azure Website.

It doesn’t matter if you’ve just added the new account to Azure PowerShell.

It doesn’t matter if you’ve not used your old Azure Tenant in a while.

It Does matter what Azure PowerShell has as your Default, or Current Subscription at the time of calling your Azure cmdlets.

What do I mean?

Well before I explain Current/Default Subscription, let’s get those who have never used PowerShell Azure with their Azure account set up.

  1. Open your Azure PowerShell window
  2. Type
    Add-AzureAccount

    press enter.

  3. You will be asked to sign into your Azure account.

  1. Once done, PowerShell window will display your Id, Type of account, Subscriptions, Tenants.

If you now type

Get-AzureSubscription

it will state that your Subscription is the Default and Current. Default means every time you open your PowerShell window, it will already have this subscription loaded. Current means, if you have multiple subscriptions the one marked as current is the one you are performing your cmdlets against.

Selecting the Azure Subscription to work against.

So what to do? I’ve added a second, free trial azure subscription to powershell azure by calling the

Add-AzureAccout


cmdlet again. I’ve then called

Get-AzureSubscription


again to list all subscriptions. (Please note, you might have one account that have several subscriptions, it’s not a one to one relationship, just happens to be for me).

As you can see from the above image, the Subscription Name “Windows Azure MSDN – Visual Studio Ultimate” is both the default and the current subscription.

I can make the Subscription Name “Free Trial” my current subscription by calling (Note: Subscription name is case sensitive)

Select-AzureSubscription –SubscriptionName "Free Trial"

NOTE: (Just learnt this myself!!) If you have version 0.8.14 of PowerShell Azure you can call by Subscription ID instead. This will help for people with Multiple Subscriptions with the same name. Not sure which version this was implemented but in 0.8.3 it wasn’t there. Find out your version by typing (get-module azure).version

Select-AzureSubscription –SubscriptionId [SubscriptionID]

Now if you type

Get-AzureSubscription

you can see that my Free Trial is now Current.

To make the Free Trial default, all I need to do is add –Default to the Select-AzureSubscription cmdlet

Select-AzureSubscription  -SubscriptionName "Free Trial" –Default

Hopefully now you won’t end up making changes to the wrong Azure instance like I did originally.

Using powershell to Apply the DisableLoopbackCheck registry fix


Open Windows Powershell as an administrator.
 
Run the following powershell command.

$regkeyPath = "HKLM:\System\CurrentControlSet\Control\Lsa"
$key = "DisableLoopbackCheck"
New-ItemProperty -Path $regKeyPath -Name $key -Value "1" -PropertyType dword