Export your folder structure to XML using Powershell

2024-01-28

SharePoint has a lot of options allowing you to organize your files. Some of them really amazing. Folders are useful and your users have been familiar with them for a long time before SharePoint, but they aren’t necessarily the best way of organizing your files. There is a lot of reasons why not to use them.

But once you have started using SharePoint folders and your users happily created unstructured, repeating, unrecoverable folder mess, you may want to audit them, see the structure of your SharePoint library and find out if you can reorganize, restructure or replace SharePoint folders with managed metadata.

Retrieve folders

Get folders from SharePoint list or library.

Step 1: Add paths to SharePoint Online SDK

Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"

Step 2a: Connect to SharePoint Online

Use this step if you are working with SharePoint Online. The rest of the steps are exactly the same for both environments. Create client context and test the connection.

$ctx=New-Object Microsoft.SharePoint.Client.ClientContext($Url)
$ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, $AdminPassword)
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()

Step 2b: Connect to SharePoint Server

Use this step if you are working with SharePoint Server. The rest of the steps are exactly the same for both environments.

$ctx=New-Object Microsoft.SharePoint.Client.ClientContext($Url)
$ctx.Credentials = New-Object System.Net.NetworkCredential($Username, $AdminPassword)
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()

Step 3: Load folders

Load first level folders.

$OriginalList=$ctx.Web.Lists.GetByTitle($OriginalLibrary)
 
   # get first level folders from a library
   $folderCollectionFirstLevel=$OriginalList.RootFolder.Folders
   $ctx.load($OriginalList)
   $ctx.Load($folderCollectionFirstLevel)
   $ctx.ExecuteQuery()   

The folders may reach several layers down. Some folders may have hundreds of nested folders, some may stop at first level. You can go through such irregular tree structure using recursion:

foreach($folder in $folderCollectionFirstLevel)
{ 
    Get-folders -higherLevelFolder $folder -OriginalLibrary $OriginalLibrary      
}

where Get-folders is the name of your function calling itself.

Create XML

Step 1: Create XML Document

$xmlWriter = New-Object System.XMl.XmlTextWriter($XMLPath,$Null)
$xmlWriter.WriteStartDocument()
$xmlWriter.WriteComment('Get the Information about the folder structure')
$xmlWriter.WriteStartElement('Folders')
$xmlWriter.Flush()
$xmlWriter.Close()
 
$xmlDoc = [System.Xml.XmlDocument](Get-Content $XMLPath);
$xmlDoc.Save($XMLPath)

Step 2: Create xml node

This step would be easy if we had only one level of folders. It would look like that:

$folderNode = $xmlDoc.CreateElement("Folder")
$xmlDoc.SelectSingleNode("/Folders").AppendChild($folderNode)

We are dealing, however, with irregular folder structure, sub-folders going maybe hundreds of levels down. We also cannot assume that all folder names will be unique. Partial folder structure may be copied in several places, e.g. folder Invoices in folder Payments may appear in both HR folder and Accounting folder, i.e. Accounting\Payments\Invoices and HR\Payments\Invoices. That means that comparing just the name of a parent folder or even two levels up folder is not a solution. We need to follow the exact path.

Step 3: Find the parent

The solution starts by counting the number of levels, which corresponds to the number of slashes in ServerRelativeUrl. That allows later to decide at what level the folder is and how deeply we iterate through the nodes:

$perspectivenode = "//Folders/"+ $folderServerRelativeUrl.Replace($libraryTitle,"")
$countOfLevels = ($perspectivenode.Length - ($perspectivenode.replace("/","").Length))-3

ServerRelativeUrl of a sample folder: /test/fold1/fold1-1 $perspectivenode: //Folders/fold1/fold1-1

Fig1. ServerRelativeUrl, $perspectivenode variable and created node

The root in the xml document is called Folders.

$tempRelativeUrl=$perspectivenode.Replace("//Folders/","")
$node = $XMLDoc.SelectSingleNode("Folders")

Go through all the levels:

for($level=0; $level -lt $countOfLevels; $level++)
{
   $nameOfTheUpperFolder = $tempRelativeUrl.Remove($tempRelativeUrl.IndexOf("/"))
   Write-Host "nameoftheupperfolder " $nameOfTheUpperFolder
   $node=$node.SelectNodes("Folder[@Name='$nameOfTheUpperFolder']")
 
   $tempRelativeUrl = $tempRelativeUrl.Remove(0,($tempRelativeUrl.IndexOf("/")+1))
   Write-Host "temprelativerl :" $tempRelativeUrl
}

Step 4: Append child

Each of my items is a folder, so I can hardcode it here.

$folderNode = $xmlDoc.CreateElement("Folder")
$node.AppendChild($folderNode)

If I were adding different elements, e.g. files and folders, or would like to use different languages ( Ordner in German, etc.), the code would look like this:

$newNode = $xmlDoc.CreateElement($nameOfNewNode)
$node.AppendChild($newNode)

Step 5: Add attributes

You can add more or remove these if you don’t need them for your scenario.

$folderNode.SetAttribute("Name", $folderName)
$folderNode.SetAttribute("NumberOfSubfolders", $noOfSubfolders)
$folderNode.SetAttribute("ItemCount", $folderItemCount)
$folderNode.SetAttribute("ServerRelativeUrl", $folderServerRelativeUrl)

Step 6: Save the document

The last step saves all our changes.

$xmlDoc.Save($XMLPath)

Sample results

See Also

Audit folder structure to XML

Audit Sharepoint folder structure to XML (SharePoint Server)

Audit SharePoint Online folder structure to XML

Get the structure of your SharePoint library (folders + files) to XML