We can all agree that drag-and-drop upload is one of the greatest conveniences in SharePoint Online. You can select multiple files or entire folders and drag them directory to your SharePoint Online document library and watch the items get uploaded. No added client or tools, only you and the browser.
But when it comes to unattended jobs and automation, drag and drop is not an option. For example, if you want to schedule an upload of a specific file or folder to the SPO document library, you’ll need to do it programmatically. Lucky for you, this article will teach you how to upload files to SharePoint using PowerShell.
Table of Contents
Prerequisites
Here are the things you’ll need.
- A computer with Windows PowerShell 5.1 or the latest PowerShell 7 installed. This post will use PowerShell 7.3.2 on Windows 11.
- Access to a SharePoint Online document library.
Install the PnP PowerShell Module
You must install the PnP PowerShell module on your computer first. This module provides cmdlets that allow you to authenticate and interact with your SharePoint Online tenant.
- Open PowerShell and run this command:
Install-Module 'Pnp.PowerShell'
- Verify that the module is installed:
Get-Module 'Pnp.PowerShell' -ListAvailable
As you can see, the latest version as of this writing is 1.12.0.
- Assuming that you have not connected to SharePoint using the Pnp.PowerShell module, you must first register the management shell access to Azure AD using the below command:
Register-PnpManagementShellAccess
An interactive login window pops up. Log in with your credentials.
- Next, you’ll be asked to consent to the PnP Management Shell required permissions. Scroll to the bottom and click Accept.
Before we try to upload files to SharePoint using PowerShell, let’s prepare the details.
- What is the target SharePoint site URL for the upload?
- In this example, the target SharePoint site URL is https://.sharepoint.com/sites/SalesandMarketing.
- What is the target document library relative to the site URL?
- The target document library (folder) is DummyDocs.
- What is the location of the files to upload?
- Suppose you have one or more files that you want to upload to a SharePoint document library. For example, I have ten files under the ./temp folder.
Now that you have all the details let’s dive into the upload process.
Open PowerShell and store the following variables.
# What is the target SharePoint site URL for the upload? $spoSite = 'https://<tenant>.sharepoint.com/sites/SalesandMarketing/' # What is the target document library relative to the site URL? $spoDocLibrary = 'DummyDocs' # What is the location of the files to upload? $localFolder = "./temp"
Now, connect to the SharePoint Site URL by running this command. This command authenticates to the SPO site you specified in the $spoSite variable. Enter your username and password when prompted.
Connect-PnPOnline -Url $spoSite
Run this command to ensure you’re connected to the correct SPO site.
Get-PnPSite
Next, confirm that the target folder you specified in the $spoDocLibrary variable exists in the SPO site.
Resolve-PnPFolder -SiteRelativePath $spoDocLibrary
The result below confirms that the target folder exists.
Let’s now gather the files to upload.
$files = Get-ChildItem $localFolder -File
Once the files are loaded into the $files variable, let’s use iteration to upload each file. We’ll use the foreach loop in this example to upload each file.
What ultimately uploads the files to the target is the Add-PnPFile cmdlet.
foreach ($item in $files) { Add-PnPFile ` -Path ($item.FullName.ToString()) ` -Folder $spoDocLibrary ` -Values @{"Title" = $($item.Name) } }
Each item appears on the screen as they get uploaded to the document library.
Finally, open the document library in your browser and see the new files you uploaded.
In the previous section, we demonstrated uploading files under a folder. What if you need to upload files recursively and want to keep the folder structure?
For example, the below screenshot shows that the ./temp folder has a child folder called subdir1. Both the parent and child folders contain ten files each.
The goal is to upload these folders and files to the SharePoint document library, retaining the sub-directory structure.
Let’s use the same information as we did in the previous section. Run the below code in PowerShell, but ensure to replace the values with yours.
# What is the target SharePoint site URL for the upload? $spoSite = 'https://<tenant>.sharepoint.com/sites/SalesandMarketing/' # What is the target document library relative to the site URL? $spoDocLibrary = 'DummyDocs' # What is the location of the files to upload? $localFolder = "./temp"
Connect to the SharePoint Online Site
Connect-PnPOnline -Url $spoSite -Credentials $spoCredential
Once connected, let’s first upload the files on the parent level. The below command is the same one we ran in the previous section to upload the files from one folder.
$files = Get-ChildItem $localFolder -File foreach ($item in $files) { Add-PnPFile ` -Path ($item.FullName.ToString()) ` -Folder $spoDocLibrary ` -Values @{"Title" = $($item.Name) } }
And that takes care of the parent folder upload.
But where’s the subfolder? That’s what we’ll upload next.
Run this following command to get all child folders under the $localFolder location.
# Get all child folders. $childFolders = Get-ChildItem -Path $localFolder -Directory -Recurse
Now, run this command to upload the files recursively. You do not have to pre-create the subfolders in the document library; the Add-PnPFile cmdlet already does that for you.
# Loop through each child folder foreach ($childFolder in $childFolders) { # Form the target subfolder name (i.e. "DummyDocs/subdir1") $TargetSubFolderName = ` "$($spoDocLibrary)$(($childFolder.FullName ).Replace((Resolve-Path $localFolder).Path," ).Replace('\','/'))" # Get all files in the child folder $files = Get-ChildItem ($childFolder.FullName) -File # Loop through each file under the child folder. foreach ($item in $files) { Add-PnPFile ` -Path ($item.FullName.ToString()) ` -Folder $TargetSubFolderName ` -Values @{"Title" = $($item.Name) } } }
You’ll see a similar display as the screenshot below during the file upload.
Once the upload is complete, you can verify that the subfolder was created in the document library.
And the files were also uploaded to the subfolder, as shown below.
Putting It All Together in a Script
So far, we’ve demonstrated the steps for uploading files and folders so that you can grasp how the process works (hopefully). What’s even better is turning this whole process into a script.
First, save the code below as Start-FolderUpload.ps1 on your computer.
Note. You can also download the script from this GitHub Gist.
[CmdletBinding()] param ( [Parameter(Mandatory)] [pscredential] $Credentials, [Parameter(Mandatory)] [string] $SiteUrl, [Parameter(Mandatory)] [string] $LocalFolderPath, [Parameter(Mandatory)] [string] $TargetFolderName, [Parameter()] [Switch] $Recursive ) # Ensure that the LocalFolderPath exists. Exit if not. if (!$(Test-Path $LocalFolderPath)) { "The LocalFolderPath does not exist." | Out-Default return $null } else { $LocalFolderPath = $(Resolve-Path $LocalFolderPath) } # Connect to the SPO site. Exit if failed. try { Connect-PnPOnline -Url $SiteUrl -Credentials $Credentials -ErrorAction STOP } catch { $_.Exception.Message | Out-Default return $null } # Ensure that the Document Library exists. Exit if not. try { $null = Resolve-PnPFolder -SiteRelativePath $TargetFolderName -ErrorAction Stop } catch { $_.Exception.Message | Out-Default return $null } # Upload the top-level folder files only. $Files = Get-ChildItem -Path $LocalFolderPath -File foreach ($File in $Files) { Add-PnPFile -Path ($File.FullName.ToString()) -Folder $TargetFolderName -Values @{"Title" = $($File.Name) } | Out-Null "Uploaded File: $($File.FullName)" | Out-Default } # If -Recursive, upload the subfolders and files if ($Recursive) { $SubFolders = Get-ChildItem -Path $LocalFolderPath -Directory -Recurse foreach ($SubFolder in $SubFolders) { $SubTargetFolderName = "$($TargetFolderName)$(($SubFolder.FullName).Replace($LocalFolderPath,'').Replace('\','/'))" $Files = Get-ChildItem -Path ($SubFolder.FullName) -File foreach ($File in $Files) { Add-PnPFile -Path ($File.FullName.ToString()) -Folder $SubTargetFolderName -Values @{"Title" = $($File.Name) } | Out-Null "Uploaded File: $($File.FullName)" | Out-Default } } }
This script has five parameters, four of which are mandatory.
- -Credentials — This parameter accepts your SharePoint Online credential object.
- -SiteURL — The SharePoint Online site URL.
- -LocalFolderPath — The location on your local computer containing the files to upload.
- -TargetFolderName — The target folder or document library name.
- -Recursive — The switch parameter enables the recursive files and folders to upload. Only the files on the parent folder level will be uploaded if not specified.
Let’s put the script to the test.
$credentials = Get-Credential .\Start-FolderUpload.ps1 ` -Credentials $credentials ` -SiteUrl 'https://<tenant>.sharepoint.com/sites/sitename' ` -TargetFolderName' DummyDocs'` -LocalFolderPath' ./temp'` -Recursive
And watch the magic as it happens!
Conclusion
PowerShell and SharePoint Online interaction through the PnP module is an excellent way to automate multiple tasks. You’ve learned in this post how to get started by uploading files and folders from a local location to a SharePoint Online document library.
Additionally, we provided a working script that you can use to upload files to SharePoint using PowerShell. Optionally, the script lets you specify whether to recursively upload files while maintaining the subfolder structure, giving you additional control.
Finally, one thing to remember is that the Add-PnPFile cmdlet does not tell you if the file with the same name already exists in the target library. If versioning is enabled in that document library, a new version of that file will be created. In contrast, if the versioning is disabled, the file will be overwritten without warning.
2 comments
does this script work with scheduled Tasks?
Are the credentials cached or is the management shell due to aprooval linked with a token?
You must use certificate-based authentication instead of the interactive username+password for unattended use.