About Ian Hayse

SharePoint Admin. SharePoint Guy. I have been working with SharePoint nonstop for the past nine years. In those nine years, I've done everything from 'normal' Admin work, development, migrations, training, planning, search, workflow, InfoPath, to crazy fixing of the hands-off SharePoint databases. I'm located in Austin, TX and can always help out with small projects.

How to use an iFrame in a modern SharePoint Online page

Using the Embed web part I was trying to paste in a site URL when I should have been using the iFrame HTML tag.

example:

<iframe src="https://sharepointed.com" height="200" width="300"></iframe>

If you encounter this error: This website doesn’t support embedding using just the address ….

You will need to update the HTML Field Security settings in the Site Settings area of your site. In my case, I simply added sharepointed.com to the Allow iframes from this domain list, then updated the web part again.

Connect to SharePoint Online Using PowerShell

In this example, I’m connecting to a Site Collection on my tenant.

Assumptions:
1) You have created a token in your o365 site
1.1) https://portal.office.com/account/
1.2) On the left site of the page click Security & privacy, then click Create and manage app passwords
1.3) In the app password page click the create button and give it a name.
1.4) Save the password to a secure location.
1.5) There is a better way of doing this that I will cover in a future post.
2) You have downloaded to CSOM DLL(s) from Nuget

Clear-Host

$userName = "ihayse@sharepointed.com"
$pw = "abc123taco"
$siteCollectionUrl = "https://sharepointed.sharepoint.com/sites/taco"

#Secure the password
$securePassword = ConvertTo-SecureString $pw -AsPlainText -Force

Add-Type -Path "C:\Code\DLL\Microsoft.SharePoint.Client.dll"
Add-Type –Path "C:\Code\DLL\Microsoft.SharePoint.Client.Runtime.dll"

#Create a Context
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteCollectionUrl)

#Authorise
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userName, $securePassword)

$web = $ctx.Web
$properties = $web.AllProperties
$ctx.Load($web)
$ctx.Load($properties)
$ctx.ExecuteQuery()

Write-Host " Site Collectione URL: $($web.Url)"
Write-Host "Properties are "

foreach ($prop in $properties) {
    $prop.FieldValues
}

Use Flow to Update SharePoint Hyperlink Field

If you want to update a hyperlink field in SharePoint Online using Flow, you will need to use the Send and HTTP request to SharePoint action (for now). I first tried using the Update item Flow action but it would not correctly set the URL and Description values. One other issue I had was using the Post method opposed to Patch. Every blog / forum post was suggesting the use of the Post method but the Patch method is what I ended up using.

For testing, create a list titled Contractors, in the list add a Hyperlink field titled website. In your Flow add a Send HTTP request to SharePoint action.

Site Address: select the site you created the Contractors list in.
Method: PATCH
Uri: _api/web/lists/getbytitle(‘Contractors’)/Items(‘ID From above Action’)
Headers
Accept application/json
Content-Type application/json; odata=verbose
X-HTTP-TYPE MERGE
IF-MATCH *
Body

{
'__metadata': { 'type': 'SP.Data.ContractorsListItem' },
   'website':
   {
          '__metadata': {'type':'SP.FieldUrlValue'},
               'Description':'Title from above Action',
                'Url': 'http://www.bbb.com'
  }
}

Peek code:

{
    "inputs": {
        "host": {
            "connection": {
                "name": "@parameters('$connections')['shared_sharepointonline']['connectionId']"
            }
        },
        "method": "post",
        "body": {
            "method": "PATCH",
            "uri": "_api/web/lists/getbytitle('Contractors')/Items('@{triggerBody()?['ID']}')",
            "headers": {
                "Accept": "application/json",
                "Content-Type": "application/json; odata=verbose",
                "X-HTTP-TYPE": "MERGE",
                "IF-MATCH": "*"
            },
            "body": "{\n'__metadata': { 'type': 'SP.Data.ContractorsListItem' },\n   'website':\n   {\n          '__metadata': {'type':'SP.FieldUrlValue'},\n               'Description':'@{triggerBody()?['Title']}',\n                'Url': 'http://www.bbb.com'\n  }\n}"
        },
        "path": "/datasets/@{encodeURIComponent(encodeURIComponent('https://EnterYourURL.sharepoint.com/sites/spdev'))}/httprequest",
        "authentication": "@parameters('$authentication')"
    },
    "metadata": {
        "flowSystemMetadata": {
            "swaggerOperationId": "HttpRequest"
        }
    }
}

Get 5 Most Recently Update Items From ALL Lists in a FARM

A wise man recently asked me how I’d go about retrieving the five most recent modified items from every list in a farm. Fun question and there are a couple ways of going about this, but here is what I came up with.

Things to note: 1) on prem farm with more than one web app. 2) if you are dealing with a large farm, I’d suggest chunking out the Get-SPSite -limit all and the $site.allwebs into to smaller data sets. 3) script needs comments and error handling or reporting

Clear-Host

if ($null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) {
    Add-PsSnapin Microsoft.SharePoint.PowerShell
}

$sites = Get-SPWebApplication | Get-SPSite -limit all 

foreach ($site in $sites) {
    foreach ($web in $site.AllWebs) {
        $lists = $web.Lists
        foreach ($list in $lists) {
            $query = New-Object Microsoft.SharePoint.SPQuery 
            $query.Query =  
            "<Query>
                <OrderBy>
                  <FieldRef Name='Modified' Ascending='False' />
                </OrderBy>
            </Query>" 
            $query.RowLimit = 5
            $listItems = $list.GetItems($query)
            
            if ($listItems) {
                foreach ($lIem in $listItems) {
                    Write-Host "Web: $web -- List: $list -- Item: $($lIem.Name) "
                }
            }
        }
        if ($web) {
            $web.Dispose()
        }
    }
}

Using SharePoint Keyword Query to Search Across Site Collections

Quick and easy way to search for an item across site collections. I would suggest using one of the Keyword query tools to fine tune your queries. Also note that SharePoint will limit your search results to 10,000 items, but you can page your results and cycle through them. In the example below, I’m searching across all the site collections off of the /sites/ managed path. With the returned dataset, I’m looping through the rows getting the SPFile of each row.

$site = New-Object Microsoft.SharePoint.SPSite "https://example.site.com"

$keywordQuery = New-Object Microsoft.office.Server.Search.Query.KeywordQuery $site

$queryText = "SomeField:Taco AND Path:https://example.site.com/sites/*"
$keywordQuery.QueryText = $queryText
$keywordQuery.TrimDuplicates = $false
$searchExec = New-Object Microsoft.Office.Server.Search.Query.SearchExecutor
$searchResults = $searchExec.ExecuteQuery($keywordQuery)

$dTable = $searchResults.Table[000].Table.Rows

foreach($row in $searchResults.Table[000].Table.Rows)
{
      $web = Get-SPWeb $row.SPWebUrl
      $file = $web.GetFile($row.Path)
      Write-Host $file.ServerRelativeUrl
}

Use Selenium to Upload a File to SharePoint

I created a Visual Studio console app.
Added the Selenium WebDriver and the Selenium Chome WebDriver Nuget packages

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;

namespace SeleniumUpload
{
    class Program
    {
        static void Main(string[] args)
        {
            //set span to 20 seconds
            System.TimeSpan span = new System.TimeSpan(0, 0, 0, 20, 0);
            
            //load chrome and the sharepoint library you want to upload to
            IWebDriver driver = new ChromeDriver();           
            driver.Url = "https://yoursite/yourlibrary/Forms/AllItems.aspx";

            //click the upload button
            driver.FindElement(By.Id("QCB1_Button2")).Click();

            //switch the driver focus to the file upload modal
            driver.SwitchTo().Frame(driver.FindElement(By.ClassName("ms-dlgFrame")));

            //allow some time for the modal to load 
            WebDriverWait wait = new WebDriverWait(driver, span);

            //get the upload field from the sharepoint modal
            IWebElement uploadElement = driver.FindElement(By.Id("ctl00_PlaceHolderMain_ctl02_ctl04_InputFile"));

            //use sendkeys to insert the path to the file you want to upload
            uploadElement.SendKeys(@"C:\taco.txt");

            //click the ok button on the modal
            driver.FindElement(By.Id("ctl00_PlaceHolderMain_ctl01_RptControls_btnOK")).Click();            
        }
    }
}

PowerShell and CSOM Bulk Delete List Items

From my desktop, I wanted to delete all the items in a list. The list had 10,000+ item and loop through the items one at a time is slow and not efficient. This is where batch process comes in and will help to quickly delete all the items in a large list.

In this example, I commented out the SharePoint Online Credentials because I’m working with on on-prem environment.
The batch size is set to 1,000

#Load SharePoint CSOM Assemblies (you will need to grab these from a SharePoint server)
Add-Type -Path "C:\SharePoint DLL\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\SharePoint DLL\Microsoft.SharePoint.Client.Runtime.dll"

$SiteURL = "https://mylocalsite/sites/taco/"
$ListName = "GiantList"
$BatchSize = "1000"

Try {
#$Cred= Get-Credential
#$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)

#Setup the context
$Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
#$Ctx.Credentials = $Credentials

#Get the List
$List = $Ctx.Web.Lists.GetByTitle($ListName)
$Ctx.Load($List)
$Ctx.ExecuteQuery()

#Define Query to get List Items in batch
$Query = New-Object Microsoft.SharePoint.Client.CamlQuery
$Query.ViewXml = @"




$BatchSize

"@

#Get List Items in Batch
Do
{
$ListItems = $List.GetItems($Query)
$Ctx.Load($ListItems)
$Ctx.ExecuteQuery()

if ($ListItems.Count -gt 0)
{
for ($i = $ListItems.Count-1; $i -ge 0; $i--)
{
$ListItems[$i].DeleteObject()
}
$clientContext.ExecuteQuery()
}
else
{
Write-Host "." -foregroundcolor black -backgroundcolor yellow
$continue = $false;
}

$Query.ListItemCollectionPosition = $ListItems.ListItemCollectionPosition
}
While($Query.ListItemCollectionPosition -ne $null)

}
Catch {
write-host -f Red "Error Adding Items to List!" $_.Exception.Message
}

Limiting the number of rows returned in each query will help to avoid this error:

Exception calling "ExecuteQuery" with "0" argument(s): "The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator."

Some of the scripts was borrowed from:
http://www.sharepointdiary.com/2016/12/sharepoint-online-get-all-items-from-large-lists-powershell-csom.html

Note:
I plan to revisit this and batch processing where you combine all the delete statements into a large XML string and pass it to SharePoint for processing.

Copy or Move Folder Structure in SharePoint Using CSOM

This post is intended for SharePoint Online and 2016+ using CSOM.

In previous version of SharePoint, moving a folder structure to a different library or a different library in another site collection required a good amount of code and effort. With the introduction of Microsoft.SharePoint.Client.MoveCopyUtil we now have the ability to use methods like MoveFolder to perform a cut and paste of a folder structure. There are other methods that are worth exploring like FolderCopy that is a copy and paste of a structure.

More methods can be found here: MoveCopyUtil members

Name Description
Public method Static member CopyFile
Public method Static member CopyFileByPath
Public method Static member CopyFolder
Public method Static member CopyFolderByPath
Public method Static member MoveFile
Public method Static member MoveFileByPath
Public method Static member MoveFolder
Public method Static member MoveFolderByPath

Example of moving a folder between site collections.


string dstUrl = "https://sharepoint.com/sites/B/";

using (ClientContext srcContext = new ClientContext(dstUrl))
{
  string sourceFolder = "https://sharepoint.com/sites/A/libraryname/foldername";
  string destFolder = dstUrl + "libraryname/foldername";

  MoveCopyUtil.MoveFolder(srcContext, sourceFolder, destFolder);
  srcContext.ExecuteQuery();
}

Again, this is using CSOM with the latest version of the NuGet package (16.0.4351.1000).

Move OneNote Notebooks to New SharePoint Library or Server

Problem:
My company recently completed a SharePoint 2010 to 2016 migration. With the migration came the use HTTPS security, so my OneNote notebooks stored in SharePoint would no longer sync. All of my notebooks displayed an error of Not syncing.

Here is how I fixed the issue:
First, make sure all of your notebooks are in SharePoint.
In OneNote, click on the File tab.
Locate the first notebook you want to update.
Next to the notebook name click Settings, then Properties.
In the Properties window click on Change Location…
Copy the URL of your SharePoint document library.
Paste the URL into the OneNote Chose a sync location… window.
Select the folder you want to sync OneNote with.
Click OK, a message box should appear saying the item is syncing.
Give it a minute and you should be set.

SharePoint SPFx PNP gulp trust-dev-cert Error

Trying to setup my dev environment to play around with SPFx and ran into this error:

[08:33:19] Error - [trust-cert] Error: root "Trusted Root Certification Authorities"
Signature matches Public Key
CertUtil: -addstore command FAILED: 0x80070005 (WIN32: 5 ERROR_ACCESS_DENIED)
CertUtil: Access is denied.

[08:33:19] Error – [trust-cert] Certificate trust failed with an unknown error.
[08:33:19] Error – ‘trust-cert’ sub task errored after 5.61 s
“Error trusting development certificate.”
[08:33:19] ‘trust-dev-cert’ errored after 5.62 s
[08:33:19]
[08:33:19] ==================[ Finished ]==================
Error – [trust-cert] Error: root “Trusted Root Certification Authorities”
Signature matches Public Key
CertUtil: -addstore command FAILED: 0x80070005 (WIN32: 5 ERROR_ACCESS_DENIED)
CertUtil: Access is denied.

Error – [trust-cert] Certificate trust failed with an unknown error.
Error – ‘trust-cert’ sub task errored after 5.61 s
“Error trusting development certificate.”

The only way I could manage to get around it was to manually create a cert using the OOB Windows tools.

Creating your .cer file

  1. Start by locating makecert.exe. You can typically find it in and C:\Program Files (x86)\Windows Kits\8.1\bin\x64 on Windows Server 2012 R2.
  2. Browse to that location with Command Prompt with Administrative privileges.
  3. You can actually create a certificate to run against your machine name and you can also have it work against localhost. To create a certificate for your machine name, use the following command:

makecert -n “CN=%ComputerName%, CN=localhost” -ss MY -sr LocalMachine -b 08/09/2016 -e 09/09/2028 -a sha256 -sky exchange -r -pe myCert.cer

Return to where you tried to run trust-dev-cert and move to the next step.  We now have a dev cert so there is no use in creating one using gulp.

Thanks to Santi Murtagh for the instructions on setting up the cert.

Creating a Self-Signed SSL Certificate for Developing and Testing Against IIS