Building robust connection logic for PowerShell PnP scripts

Building robust connection logic for PowerShell PnP scripts

Diving into the world of PowerShell scripts and modules, you'll discover the art of managing connections with Microsoft 365 – a skill essential to ensure things go smoothly!

Whether dealing with a complex script that demands intricate connection handling within a function or module or opting for a more centralised approach for the main script to tackle, the choice is yours. However, the ability to handle these connections and implement the logic that governs them is crucial. So join us as we explore the best practices for making and maintaining these vital connections!

Why bother developing connection logic?

Firstly, stopping a script before it is executed without authentication is critical for correct flow. Without authentication, a script will most likely fail to achieve its goal but still attempt to run later code without the needed authorisation, and worse, repeat this over and over if it is in a loop, for example. By implementing stringent connection logic, we ensure that scripts only run when proper authentication is in place, maintaining a robust flow to its execution.

Next, checking if the script can access the resources is fundamental to its successful execution. A script without the appropriate permissions may fail silently or trigger unexpected errors, leading to a significant waste of time and resources in troubleshooting. By validating access right at the outset, you can streamline the process and prevent unnecessary complications.

Lastly, implementing retry logic and the ability to deal with various error codes adds resilience and flexibility to your PowerShell scripts. In the real world, connections can falter, servers can become temporarily unavailable, and unexpected issues can arise. By building mechanisms to retry connections and handle various error codes, your scripts become more robust and capable of adapting to these challenges.

Example PowerShell scripts

The below scripts were tested using the latest version of PnP.PowerShell (2.2.0) at the time of writing this article.

The following code was used to set up any required variables and preferences to ensure these examples work if you test them yourselves. Below I use the Windows Credential Manager but feel free to connect however you like.

$Global:DebugPreference = "Continue"

$siteUrl = "https://[TENANT].sharepoint.com/sites/[SITE]"
$credentialManagerName = "[EMAIL]@[TENANT].onmicrosoft.com"
$credentials = Get-PnPStoredCredential -Name $credentialManagerName
$username = (Get-PnPStoredCredential -Name $credentialManagerName).UserName

Initiating the connection

The code snippet is a practical example of initiating the connection process and ensuring the context is returned to PowerShell to allow us to check it later - this part is critical. If, for example, the credentials don't exist, the password is incorrect, or you require an interactive login (MFA or conditional access restrictions), then this will catch them.

# Connect to site
Try {
    # Intialize connection
    Write-Debug "Connecting to site: $siteUrl"
    Write-Debug "Connecting with user: $username"
    $ctx = Connect-PnPOnline -Url $siteUrl -Credentials $credentials -ReturnConnection -ErrorAction Stop

    # Validate connection and access rights
    Write-Debug "Attempting to access site."
    $site = Get-PnPSite -Connection $ctx -ErrorAction Stop

    # Successfully connected to site
    Write-Debug "Successfully connected to: $($site.Url)"
}

As you can see, it logs the connection process starting and initiates the command to connect to SharePoint. This will check the baseline authentication works. If this part is successful, it will then attempt to fetch the site details, which checks the connection worked and has permission to access the relevant resources.

Catch errors relating to non-interactive logins

As mentioned above, you may find a service account needs to be correctly set up or a conditional access policy prevents you from using a non-interactive means to authenticate. This might be because MFA is required, which may require the UI to authenticate.

# Catch any errors related to non-interactive login
Catch [Microsoft.Identity.Client.MsalUiRequiredException] {
    Write-Host "ERROR: A policy may be blocking non-interactive login for the user." -ForegroundColor Red
    Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
    return
}

Catch errors relating to access

The most common error I usually see is if you are a SharePoint administrator or have been granted permissions at a certain level but need all the permissions to execute a script. In this case, the below catch is designed to work with the Get-PnPSite command, which will return the exception below.

# Catch any errors related to access denied
Catch [Microsoft.SharePoint.Client.ServerUnauthorizedAccessException] {
    Write-Host "ERROR: Access denied for user." -ForegroundColor Red
    return
}

A nice bonus is that you can then handle the logic to add your account to the correct permission groups to execute your script without interference.

Catch any other errors

By no means is the above a complete coverage of all the possible errors you might encounter, but remember to incorporate the ones that are most likely to happen and the ones which will break the execution of your logic.

Worst case, make sure you implement a catch-all that will ensure any generic exceptions are visible and stop the execution of your script.

# Catch any other errors
Catch [Exception] {
    Write-Host "ERROR: Unable to connect to site." -ForegroundColor Red
    Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
    return
}

Final thoughts

Errors will be the bain of your life, especially as you scale up your scripts. Make sure you have a bulletproof system for catching them. As a bonus, the below code will catch all errors and inform you of the correct exception names and their details, which can be used to catch specific categories.

Catch {
    Write-Error "Caught exception of type: $($_.Exception.GetType().FullName)"
    Write-Error "Error details: $_"
}

This is part of the Toolbox Tuesday series

A range of tools and tricks for your tech toolbox, from PowerShell scripts to document templates!

View more articles