PnP.PowerShell Is Out and Here's Why it Matters

PnP.PowerShell Is Out and Here’s Why it Matters

Good things are worth waiting for. It took around two years but finally, PnP PowerShell (@PnpPowershell) for PowerShell Core, aka PnP.PowerShell is out in a preview, and GA is expected to be realized this year. Big kudos to Erwin (@erwinvanhunen) and his team. More about the journey and roadmap ‘Cross Platform PnP PowerShell Released

I am a huge fan of PowerShell and especially the PnP PowerShell library. I still use it almost daily for something, and I think scripting is something that every IT Pro should master at least on some level. As we know, things are evolving quickly, and there is always something new in the IT world. One important thing around PowerShell was PowerShell Core 6 that was announced back in 2014. The PowerShell Core is a cross-platform, free, and open source. For an old-school SharePoint geek (like me), the lack of API and modules against SharePoint Online has been the thing to keep using the Windows PowerShell. Sure, you have been able to use REST before, but the PnP PowerShell module has just been more comfortable.

But now the future is finally here for the oldies everyone. You can find the source code of this post from my GitHub repository.

Things to Consider on PnP PowerShell

Installing the new PnP PowerShell is easy, as usual. Just open the PnP Management Shell and give Install-Module PnP.PowerShell -AllowPrerelease command. Things still are in preview mode, but new cmdlets are added regularly to GitHub. Using the module is almost similar to using the classic version. Here’s an excellent place to start if needed: PnP PowerShell Overview | Microsoft Docs

On a high -level, things to remember for us who have been using the earlier version are:

  • The roadmap
    • Once the new PnP PowerShell (v4) goes to the GA (scheduled end of 2020), the old v3 version’s development will stop.
    • The plan is to archive the v3 repo during Q1 2020
    • Start to plan the migration NOW! I just did.
  • PnP PowerShell will be available for SharePoint Online only in the future!
  • PnP PowerShell runs on top of .NET Core 3.1 / .NET Framework 4.6.1. This means that you need the new cross-platform version of PowerShell to use. You can find the installation instruction from here: Installing PowerShell – PowerShell | Microsoft Docs
  • All *-PnPProvisioningTemplate cmdlets have been renamed to *-PnPSiteTemplate. This means that Get-PnPProvisioningTemplate is now, for instance, called Get-PnPSiteTemplate.
  • Classic credential-based authentication has changed.
    • More info from GitHub (link above)
    • WebLogin functionality is not available anymore.

Regarding the login, if you are not familiar with connecting to SharePoint Online using Application Permission, I think this is an excellent time to check out that option. Giving the necessary permission through an Azure AD application will give you more robust tools to control the access for many scripting and integration use cases. Application permission is also crucial to master for a reliable and secure connection for automated tasks and service scenarios. Here’s a basic walkthrough on how to get started.

PnP PowerShell with Azure Functions

I still have not covered the “Here’s Why” section of the title, so here it goes. I think (for me) the major thing of the new release is that moving the PnP library on top of .NET Core means that PnP PowerShell now works natively with Azure Functions type of services. You were, and I have, able to use it before, but the development experience was not good, and there was not an easy way to manage the ALM process for your PowerShell functions. With the new version, all the complexity is gone, and PowerShell is starting to be a real option for service development in many cloud business cases.

As an old developer, I know this will raise the hairs upright among many of my friends, but you should not underestimate PowerShell when creating integrations or services. Yes, by coding, you can do things effectively, and there are multiple cases when PowerShell is not enough.

In many organizations, the situation is that there aren’t too many people who know modern code techniques but there are many who know PowerShell. Now with PnP PowerShell and another available cmdlet and modules, people are able to do even more complex useful programs for their organization.

At the same time, we, the consultants, are responsible for developing services that our clients can use AND maintain. In many cases, there are multiple IT Pro’s that do know PowerShell, but they do not have coding experience. I always try to build my solutions to take whole control of the solution when I am gone. One of my mentors in consulting had one rule; as a consultant, do your job every day so that you may be unnecessary the next day. Therefore, I see PowerShell as a viable option in many cases. By using it, I know multiple people can maintain the solution. This is the same reason why I find Power Platform as an essential platform in the Microsoft ecosystem.

As a consultant, do your job every day so that you may be unnecessary the next day.

To get back to the point, you can now use the PnP PowerShell module easily when building Azure functions. I recommend using a Visual Studio code from the beginning so that you will adopt better ALM practices. If necessary, start your journey by creating a basic Azure function with PowerShell. Here’s a guide for that (select PowerShell as the programming language): Create your first function in Azure using Visual Studio Code | Microsoft Docs

  • You should also go through the Application Permission section mentioned above to connect to SharePoint Online in a managed way.
    • Maybe the easiest way to connect to SharePoint Online from the automated process is to use Connect-PnPOnline -Url YOUR_URL -ClientId “ClientId” -ClientSecret “Secret.”
    • Remember not to add the client id, secret, cert details, etc. straight to your code.
    • Use the Application settings of the function to store these values during the development time.
    • In production, you should store these values to Azure Key Vault and use Managed Identity to access values, but this goes over this post’s topic.
  • For this example, I did save the connection variable to the application settings of the Function App.
  • A valuable thing to learn for Azure Function development is Dependency Management.

I assume that you have a working local Azure Function development environment based on the “Create your first function in Azure using Visual Studio Code” article above. Here are the steps you need to take to have PnP PowerShell working in your function.

Using PnP PowerShell with Azure Function

  1. Create a new PowerShell based function with Visual Studio Code.
    1. I did name mine as Basic-PnPPowerShellCore.
  2. Then, and this is the most enjoyable part, you need to load the PnP.PowerShell module on your function’s environment.
    1. Open the requirements.psd1 file and add PnP.PowerShell a dependency on the project.
    2. When writing this post, the version was 0.xxx, but you should check the current one from GitHub (link above).
    3. AND THAT’S IT. No more uploading the module manually or anything like that. Things are just rolling natively.
  1. Create a new Azure AD application for your target environment.
    1. Give the necessary permission to the application. I did give full control to all sites.
    2. Also, create a secret that can be used when authenticating against the application with PnP PowerShell.
  2. I prefer creating an Azure Function App before I publish the function to make the necessary settings, but you can choose to create the application while publishing the function.
    1. Save the application id, aka client id, and the secret to your function application settings (note what was said before about production).
  3. Download the Application Settings to your local development environment.
  4. For a test function, let us do a simple service that returns the site’s URL to the questioner.
  5. Open the run.ps1 of the function created earlier.
  6. Add the necessary parameters.
    1. $env is referring to the Azure Function Application Settings that holds the necessary details to connect to SharePoint Online.
    2. Of course, you can give these values straight to your code but in production, I recommend using other options.
$connClientId = $env:ConnClientId
$connSecret = $env:ConnSecret
$siteURL = $env:O365DemoURL
  1. My simple function looks like this:
$response = ""

try {
    Connect-PnPOnline -Url $siteURL -ClientId $connClientId -ClientSecret $connSecret

    $ctx = Get-PnPContext

    $response = $ctx.Url

    Disconnect-PnPOnline
}
catch {
    $ErrorMessage = $_.Exception.Message
    Write-Host "**ERROR: *Basic-PnPPowerShellCore"
    Write-Error $ErrorMessage
}

if ($response) {
    Write-Host ("Response: " + $response)

    # Site details found!
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::OK
        Body = $response
    })
}
else {
    # No site details found
    Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
        StatusCode = [HttpStatusCode]::NoContent
    })
}
  1. Next, you can test the function by pressing F5.
    1. Because we added a new module as a dependency, the first start will take a few minutes when the module is loaded to the local environment.
  2. After the loading is done, you can test the function by calling the URL of the function.
  1. When everything goes as planned, you will see the URL of the test site in the browser.
  2. After this, you can publish the function to Azure Function app from the Visual Code Extension
  1. I recommend going into Azure Portal and open the Azure Function where you deployed the function.
    1. From the App, select Functions, and then the function you just created.
    2. Run a Test against the function to make sure it is working.
    3. Running the test will also load the dependencies for the first time so that the calls after the initial call are quicker.
  1. Finally, you could push your code to the git repository or any source control system your organization uses.

Now, imagine what type of scenarios and possibilities the integration to Azure Function is giving to a user who knows PowerShell. In my next post, I will show something interesting regarding Power Platform and PowerShell, but hopefully, this post gives you ideas to continue developing useful services quickly.

Advertisement

Save List Attachments to SharePoint Document Library

Is it possible to save list item attachments to the document library? The short answer is yes.

This question is really common, and as you might know, there isn’t an OOTB way to do it. Still, I do understand this need because having the attachment documents in the SharePoint document library brings many benefits; search and filtering, metadata, collaboration, etc. I remember, when I was looking for a solution for this, I did find many blogs about how to upload images to the SharePoint document library with Power App.

At the end of the day, the solution for this comes to the point I had in my Tech Days 2020 session ‘The Right Tool for the Job – Combine Power Platform and SharePoint.’ Know your tools and use the right tool for the job. There is no need to read the attachment blobs in Power App and then upload the data somehow. Everything can be done with Power Automate, SharePoint, and some smart additions to extend user experience with UI only customizations.

Prepare the Library

There isn’t anything special you will need to the list that has the attachments. For the document library, I like to add a field to link the documents and the item in the referring list. So, add a column called ParentID as metadata to the document library where you want to save the document. Later, we will use this column to give the users more accessible access to the documents.

Copy the Attachments

In this example, I have a simple list with few columns and a Canvas Power App with a simple. The Save button is using the basic SubmitForm to save the given details. The attachment handling is done so that the end-user uses the default attachment field functionality to save the attachment document to the list. Then we will use a Power Automate flow to move them to the document library. There are few ways to build the necessary Power Automation.

  1. You can build a flow that is triggered on SharePoint When an item is created.
    1. In this method, the user, who is using the form, will need edit permission to the list
    2. If needed, the end-user doesn’t need permission to the document library because that connection is handled with the account that is used to create the flow
    3. But in many cases, we want the end-users to have access both on the list and document library
  2. You can initialize the flow from the Power App
    1. In this case, the user will need permission both in the list and the document library

Let’s use the option two here and initialize the flow straight from the Power App. From the ribbon in the Power Apps studio, select Action -> Power Automate -> Create a new flow. This will open a new Power Automate for you that is triggered by a Power App.

  1. Add an Initialize variable action in the beginning
    1. Name can be, for example, ListItemID
    2. Type is Integer
    3. For the value, open the dynamic value panel and select Ask in PowerApps
    4. I also like to rename the action for easier recognition in later steps
  2. Then we can select a SharePoint action related to attachments
    1. There are multiply available, and we need to use the Get attachments one
    2. Connect the action to the correct SharePoint list
    3. The id parameter is coming from the variable initialized in the beginning
  1. Next, add Apply to each action under the Control category
    1. For the output, select the Body from the Get attachment step
  2. Then add a Create file action under the SharePoint category
    1. Connect the step to the correct document library
    2. Set File name from the dynamic value panel and select the DisplayName coming from the Get attachments step
    3. File Content can also be set from the dynamic values, and you can use the AbsoluteUri parameter from the Get attachments step

Finally, remember to give a name to the flow and save it. I used the name ‘Save Attachments Demo.’ After this, we can go back to the Power App and connect the flow to the application. First, select the Save button and open the OnSelect property. Then from the ribbon, select Action -> Power Automate -> select the flow we just created. If you had something in the OnSelect property, you might lose the written function, but with undo, you can get them back.

We need to modify the OnSelect property of the button with the following functions.

SubmitForm(frmMyReport);

SaveAttachmentsDemo.Run(frmMyReport.LastSubmit.ID);

  • submit form is used to send the form details to the SharePoint list
  • Then we call the flow and give the necessary details to copy the attachments

The forms in Power Apps have some data state-related properties (LastSubmited, Unsaved, Updates) we can use in the process. LastSubmited property holds all the fields and their values of the last submitted form. We will send the list item ID parameter to the Power Automate. This id is then saved in the ListItemID variable at the beginning of the flow. Test the form with few attachments, and after the submission, the flow should copy the list attachments to the document library.

Finetuning

At this point, we can copy the attachments to the document library, but this leaves us two copies in different places, which is not ideal. Let us make some additions to the flow to overcome this.

The next step is to add the list item id as metadata to the document. This can be done with Update file properties action. The correct file is found with dynamic id fetch from the Create file action. Set the ParentID with the value in the ListItemID variable.

In the final step, we will delete the attachment from the list item. This can be done with Delete attachment action. Point the action to the list and get the item based on the ListItemID variable. The File Identifier id is fetched from the Get Attachment action.

Save the flow and make a test run with the app. Now we have a solution where the user can give list details and documents with Power Apps form. The form details are saved in the SharePoint list, and the documents are saved in a document library.

Linking the Details

So, what is the benefit of saving the id of the list item as metadata for the documents? With the id, we can build an easy functionality where users can access the desired documents straight from the list.

  1. Add a new text column to the list
    1. I like to name the column as Link to Documents
  2. Go to the document library, filter the list based on the ParentID field.
  3. Next, open the column formatting setting of the Link to Documents field
  4. Paste the following JSON to the column formatting field.

{
"$schema": "https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json",
"elmType": "a",
"txtContent": "Show Documents",
"attributes": {
"target": "_blank",
"href": "='LINK TO YOUR LIBRARY/Forms/AllItems.aspx?FilterField1=ParentID&FilterValue1=' + [$ID]"
},
"style": {
"border": "none",
"background-color": "transparent",
"cursor": "pointer",
"color": "Blue"
}
}

  1. Replace the ‘LINK TO YOUR LIBRARY’ with an URL pointing to your document library.
    1. This link in the JSON is pointing to a filtered view in the document library
    2. The FilterValue1 is set dynamically based on the ID of each list row ([$ID])
  2. Save the formatting and close the formatting panel

Now we have an easy way to navigate to the document straight from the list. When you click the Show Document link in the list, you will be directed to the documents library, and you will see only the documents related to the list item. More information on column formatting, like using the icons, can be found here: https://github.com/SharePoint/sp-dev-list-formatting