Get started with PnP-Powershell!

Welcome back to our guest blogger series. This is your chance to learn a little something about the men and women behind the screen and discover what drives them. 

Our second entry comes from our Senior Web Developer, Corrie Haffly.  With years of experience in front end design, web development, and SharePoint branding, Corrie is a vital member of our team. Join Corrie as she walks us through some of the pretty incredible things you can do in SharePoint using Powershell and PnP.

Using Powershell and PnP, we can do lots of cool things with SharePoint!

  • Upload files to different locations
  • Create lists and populate them
  • Create content types, columns/fields, terms
  • Create pages and add web parts to them
  • Apply page layouts
  • Inject Javascript
  • Apply alternate CSS
  • And more!

These are all things we’re used to doing in SharePoint Designer or with WSPs, but with PnP, we can essentially build a package that acts as a site template and push it out to update existing sites or create new sites. The possibilities are exciting.

In this tutorial, we’ll walk through a basic site provisioning package to get a feel for how this works. Download the demo files here at Github (SimpleDeploymentPackage folder).

The basic provisioning package folder structure looks like this, and can reside anywhere on your computer:

Folder structure of simple deployment package

  • Inside the simple deployment package is a Powershell script file (simple.ps1), which holds the commands that will be run in Powershell.
  • Inside the templates folder is an XML file (simple.xml), which holds the provisioning template structure.
  • Finally, there is a pageimages/sample-photo.jpg file.

By the end of this article, you’ll be able to run the Powershell script which will upload the sample-photo.jpg file to the PublishingImages folder in a SharePoint site. You won’t be making any code changes; this is just an overview to walk through the files and the process!

The provisioning template

Take a look at simple.xml.

<?xml version="1.0"?>
<pnp:Provisioning xmlns:pnp=""
    <pnp:Preferences Generator="OfficeDevPnP.Core, Version=1.6.915.0, Culture=neutral, PublicKeyToken=null" />
    <pnp:Preferences Generator="OfficeDevPnP.Core, Version=1.6.915.0, Culture=neutral, PublicKeyToken=null" />
    <pnp:Templates ID="CONTAINER-TEMPLATE-3231B31CFF0449948888AFFF43F57101">
        <pnp:ProvisioningTemplate ID="TEMPLATE-3231B31CFF0449948888AFFF43F57101" Version="1">
                <pnp:File Folder="PublishingImages" Overwrite="true" Src="pageimages\sample-photo.jpg"></pnp:File>

The <pnp:ProvisioningTemplate> element is what will hold most of the things we are concerned with. It can contain other elements that you’ll find useful, such as <pnp:SiteFields>, <pnp:ContentTypes>, or <pnp:Lists>.

In this example, though, it only contains one element: <pnp:Files />

<pnp:Files /> can also contain multiple elements for each file. In this example, we only have one file—the element for sample-photo.jpg.

Take a look at the <pnp:Files /> line of code (read the comments which are the lines preceded by a #!):

     # this is the SharePoint path - so we will be putting this file into
     # the PublishingImages folder in the site collection root
     # files can be overwritten - so if you already have this file
     # in your site, it will just be overwritten.
     # this is the file path - so it needs to use back slashes to
     # point to the file location relative to your XML file.
     # You could change the "pageimages" folder name to
     # whatever you want, as long as you change the location
     # here as well.

What if you wanted to upload two images to your SharePoint site? Simple – just copy the line of code and change the Src. Or, if you want to upload CSS files and other branding assets into the _catalogs/masterpage/ location in SharePoint, you could do that as well. Here are some examples of that kind of code below:

     <pnp:File Folder="{masterpagecatalog}/custom-branding/css"
     <pnp:File Folder="{masterpagecatalog}/custom-branding/js"

     <pnp:File Folder="PublishingImages"



  • The {masterpagecatalog} is a token that points to the master pages folder.
  • Note that the CSS and JS files will be moved into subfolders of the _catalogs/masterpage/custom-branding folder, while the logo image is being added to the PublishingImages folder.

The Powershell script

Now, let’s look at what the Powershell script file is doing.


This is a block comment at the top of the page, giving an example of what to type into the Powershell console.

This simple script adds a file into the publishing images folder of a SharePoint site.

PS C:\> $creds = Get-Credential
PS C:\> .\simple.ps1 -TargetWebUrl "" -Credentials $creds

You can do inline comments with the # symbol.

# This is the main entry point for the deployment process


This section defines the parameters that can be inputted.

     [Parameter(Mandatory = $true, HelpMessage="Enter the URL of the target web, e.g. ''")]
     [Parameter(Mandatory = $false, HelpMessage="Optional administration credentials")]    

There are two parameters, separated by the comma. The first parameter is for the URL of the site to be provisioned. The second parameter is for optional credentials.

The parameters also have to have the type of input specified (String or PSCredential), and finally the variable name ($targetWebUrl, $Credentials) which stores the input and can be used in the rest of your script.

Where does the HelpMessage turn up? When you run the script without adding parameters, Powershell will prompt you for the mandatory parameters. In this example below, I ran the “simple.ps1” script in the Powershell console. (Don’t try this at home yet—at the end of this article, I’ll take you through all the step-by-steps!)

PS C:/> .\simple.ps1

As soon as I did that, I was prompted for the targetWebUrl:

PS C:/> .\simple.ps1
cmdlet simple.ps1 at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)

I entered !? to view the help text.

PS C:/> .\simple.ps1
cmdlet simple.ps1 at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
targetWebUrl: !?

You can see that the help text was printed next into the console, and I was prompted again for the targetWebUrl.

PS C:/> .\simple.ps1
cmdlet simple.ps1 at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
targetWebUrl: !?
Enter the URL of the target web, e.g. ''

Setting the path

This next section simplifies finding your current location.

# Func: Get-ScriptDirectory
# Desc: Get the script directory from variable
function Get-ScriptDirectory
  $Invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $Invocation.MyCommand.Path

# Set current script location
$currentDir = Get-ScriptDirectory
Set-Location -Path $currentDir

Console logging

This section of code displays a message to the console.

# Confirm the environment

Write-Host -ForegroundColor Cyan "        SharePoint site collection URL: " -nonewline; Write-Host -ForegroundColor White $targetWebUrl
Write-Host ""

Write-Host allows you to write to the console.
The -ForegroundColor and -BackgroundColor parameters allow you to set the color of the text. Here’s a quick example of what that looks like:

You can print out a $variable name directly inside of a Write-Host statement.

You can alternatively print out the variable within $( ) inside quotation marks with other text.

To change colors within a message, you can use the -nonewline parameter, a semicolon, and another Write-Host statement like this:


This section of code demonstrates how to set up choices with a prompt.

$prompt = 'Please confirm that you want to continue with these settings:'

$yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes','Continues deployment'
$no = New-Object System.Management.Automation.Host.ChoiceDescription '&No','Aborts the operation'

$options = [System.Management.Automation.Host.ChoiceDescription[]] ($no, $yes)
$choice = $host.ui.PromptForChoice("Environment configuration",$prompt,$options,1)

The $choice variable is a boolean, so wrapped around the next block of code is an if statement to check whether or not the script should continue.

if ($choice){

# the rest of the code that should happen if the user says "yes"

} else {
    Write-Host " "
    Write-Host "Script cancelled by user" -foregroundcolor red
    Write-Host " "

Making things happen

Finally, the try and catch block is where the work is done to provision the template.

    Connect-PnPOnline $targetWebUrl -Credentials $Credentials
    Apply-PnPProvisioningTemplate -Path .\templates\simple.xml

    Write-Host -ForegroundColor Green "Site Structure Provisioning succeeded"
    Write-Host -ForegroundColor Red "Exception occurred!"
    Write-Host -ForegroundColor Red "Exception Type: $($_.Exception.GetType().FullName)"
    Write-Host -ForegroundColor Red "Exception Message: $($_.Exception.Message)"

In try, Powershell connects to the web site using the credentials that were entered earlier. Then, it applies the provisioning template using the provided path.

Pro tip: If you are writing your own scripts, you can comment out some of the actions (such as Apply-SPOProvisioningTemplate in the try block and add Write-Host lines to print out variables to the console. This is an easy way to error-check without having to sit through waiting for the provisioning process to run.

Try it!


  • If you haven’t yet done so, download the demo files.
  • You will need a SharePoint environment to play with. (SharePoint Online is best; SharePoint 2013 and 2016 should work as well with the latest server patches.)
  • You’ll need to make sure you have PnP-Powershell installed. Take a look at PnP-Powershell’s Github and scroll down to the Installation section find out everything you need to do.
  • If you want to check to make sure that your installation worked, open Windows Powershell and type:

    Get-Command -Module *PnP*

    If your installation is good, you’ll see a whole list of PnP-Powershell commands get listed out in the console:

  • You will also want to make sure that your execution policy is set to allow for local scripts to be run. If you aren’t sure, run Powershell as an admin (right-click on Powershell and choose Run as administrator) and type this command:


    If it does NOT say “RemoteSigned,” then type:

    Set-ExecutionPolicy RemoteSigned

    This will allow you to run local scripts.

Provision the template

  1. Run Powershell to provision the template: First, open Windows Powershell as an admin (right-click on Powershell and choose Run as administrator.
  2. CD to the folder that contains simple.ps1:
    cd "C:\yourdirectory"
  3. Run the file. To do it with prompts as described above, type .\simple.ps1
  4. To save yourself some time, enter your credentials first and then run the file with the parameters as well:

    1. $creds = Get-Credential
    2. Enter in your credentials into the dialog box.
    3. ./simple.ps1 -TargetWebUrl "" -Credentials $creds
  5. Wait. You will then see a bar across the top showing the progress of the deployment. This will take a few minutes, so go refill your coffee.

  6. When it is complete, go to your site in the browser and go to the Images folder to see the new image added to your site!

Corrie Haffly Corrie Haffly
Senior Web Developer
Sea Otter Enthusiast
Your SharePoint Tour Guide

One thought on “Get started with PnP-Powershell!

Leave a Reply

Your email address will not be published. Required fields are marked *

Human Check *

Filed Under


We are ready to discuss #MSIgnite on the #PixelMillWebinars. Join @EricOverfield, @aprildunnam, and myself for an in-depth review of Microsoft Ignite 2019 RIGHT NOW!

It’s the final countdown! Join @EricOverfield, @aprildunnam, and @DavidWarnerII for an in-depth review of Microsoft Ignite 2019 at 11am PST. #PixelMillWebinars –

Join me, @EricOverfield and @DavidWarnerII for a chat about #MSIgnite today in just a couple hours!

We are happy to announce our panel for today’s episode of the #PixelMillWebinars! Join @EricOverfield, @aprildunnam, and @DavidWarnerII for an in-depth review of Microsoft Ignite 2019 at 11am PST.

Announcing private channels in #MicrosoftTeams will be available this week! Create channels within existing teams that can only be viewed and accessed by only select members of that team. #MSIgnite