Install PDC app into SharePoint Online sites
- Last Updated: May 1, 2026
- 4 minute read
- Semaphore
- Documentation
This sample PowerShell script automates the installation of the Progress Data Cloud app into multiple SharePoint Online site collections. It is an alternative to the manual installation process described in Deploy to the SharePoint Online Site Collection.
Script overview
The Install-PdcSpoApp.ps1 script reads a list of SharePoint site URLs from a text file and installs the Progress Data Cloud app from the Tenant App Catalog into each site. For each site, the script:
- Connects to the site using PnP PowerShell.
- Finds the "Progress Data Cloud" app in the Tenant App Catalog.
- Installs the app into the site (or skips if already installed).
- Disconnects from the site.
Errors are handled per site so that processing continues for remaining sites. A final summary is printed with the count of processed and failed sites.
Prerequisites
Before running the script, ensure the following:
- Windows PowerShell 5.1 or PowerShell 7+.
- The PnP.PowerShell module is installed.
- An Azure AD application (client) ID for PnP PowerShell authentication. To improve automation, use
-ClientIdand-CertificatePath. - The Progress Data Cloud app must already be uploaded to the Tenant App Catalog. See Install the Progress Data Cloud App into your App Catalog.
- A text file with one site URL per line. Blank lines are ignored. Host-only entries (for example,
tenant.sharepoint.com) are automatically normalized tohttps://tenant.sharepoint.com.
Parameters
| Parameter | Required | Description |
|---|---|---|
InputFile |
Yes | Path to a text file containing one site URL per line. |
ClientId |
Yes | The Azure AD application (client) ID for PnP PowerShell authentication. |
AppTitle |
No | The title of the app to install. Defaults to Progress Data Cloud. |
Usage examples
Install the app to all sites in a file:
.\Install-PdcSpoApp.ps1 -InputFile .\sites.txt `
-ClientId '00000000-0000-0000-0000-000000000000'
Install with a securely stored client ID:
.\Install-PdcSpoApp.ps1 -InputFile .\sites.txt `
-ClientId (Get-Secret -Name 'MyClientIdGuid')
Input file format
Create a text file with one SharePoint site URL per line. For example:
https://tenant.sharepoint.com/sites/Finance
https://tenant.sharepoint.com/sites/Marketing
https://tenant.sharepoint.com/sites/HR
PowerShell script
<#
.SYNOPSIS
Installs the "Progress Data Cloud" app into multiple SharePoint Online sites listed in a file.
.DESCRIPTION
This script iterates through SharePoint site URLs (one per line) provided in an input file.
For each site, it:
1) Connects with PnP.PowerShell using the provided ClientId. To improve automation, use
-ClientId and -CertificatePath.
2) Finds the "Progress Data Cloud" app in the Tenant App Catalog.
3) Installs the app into the site.
4) Disconnects from the site.
Errors are handled per-site so processing continues for remaining sites. A final summary is printed.
.PARAMETER InputFile
Path to a text file that contains one site URL (or host) per line.
Blank lines are ignored. If a line looks like a host (e.g., tenant.sharepoint.com),
the script normalizes it to `https://tenant.sharepoint.com`.
.PARAMETER ClientId
The Azure AD application (client) ID used to authenticate via PnP.PowerShell's `Connect-PnPOnline`.
This is typically a GUID (e.g., 00000000-0000-0000-0000-000000000000).
.INPUTS
None. This script does not accept pipeline input.
.OUTPUTS
Information and Warning messages to the host, including a final summary of processed sites.
.NOTES
- Requires Windows PowerShell 5.1 or PowerShell 7+.
- Requires the PnP.PowerShell module.
- The app must already exist in the Tenant App Catalog; otherwise installation will fail.
.EXAMPLE
PS> .\Install-PdcSpoApp.ps1 -InputFile .\sites.txt -ClientId '00000000-0000-0000-0000-000000000000'
Installs the "Progress Data Cloud" app to each site listed in sites.txt.
#>
#Requires -Version 5.1
#Requires -Modules PnP.PowerShell
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory, Position = 0)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ -PathType Leaf })]
[string]$InputFile,
[Parameter(Mandatory, Position = 1)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[0-9a-fA-F\-]{36}#39;)]
[string]$ClientId,
[Parameter(Position = 2)]
[ValidateNotNullOrEmpty()]
[string]$AppTitle = 'Progress Data Cloud'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-TargetSites {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
$lines = Get-Content -Path $Path
if (-not $lines -or $lines.Count -eq 0) {
throw "No site entries found in '$Path'."
}
$sites = @()
foreach ($line in $lines) {
$trimmed = $line.Trim()
if ([string]::IsNullOrWhiteSpace($trimmed)) { continue }
try {
$sites += [uri]$trimmed
}
catch {
if ($trimmed -match '^[a-z0-9.-]+(\.[a-z0-9.-]+)+(/.*)?#39;) {
$candidate = "https://$trimmed"
try {
$sites += [uri]$candidate
Write-Verbose "Normalized site entry '$trimmed' to '$candidate'."
}
catch {
Write-Warning "Skipping invalid site entry after normalization: '$trimmed'"
}
}
else {
Write-Warning "Skipping invalid site entry: '$trimmed'"
}
}
}
if ($sites.Count -eq 0) {
throw "No valid site URLs to process after validation."
}
return $sites
}
function Resolve-AppFromTenantCatalog {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Title
)
$apps = Get-PnPApp -Scope Tenant -ErrorAction Stop
$app = $apps | Where-Object { $_.Title -eq $Title } | Select-Object -First 1
if (-not $app) {
throw "App titled '$Title' was not found in the Tenant App Catalog. Ensure the app is added/published before installing to sites."
}
return $app
}
function Ensure-AppInstalledToSite {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[guid]$AppId
)
$siteApps = Get-PnPApp -ErrorAction Stop
$installed = $siteApps | Where-Object { $_.Id -eq $AppId -and $_.InstalledVersion } | Select-Object -First 1
if ($installed) {
Write-Verbose "App ($AppId) already installed in this site. Skipping installation."
return
}
if ($PSCmdlet.ShouldProcess($AppId, "Install app to site")) {
Write-Verbose "Installing app ($AppId) to the site."
Install-PnPApp -Identity $AppId -ErrorAction Stop
}
}
# --- Load and validate sites ---
$sites = Get-TargetSites -Path $InputFile
# --- Process sites ---
$processed = 0
$failed = 0
foreach ($site in $sites) {
try {
Write-Verbose "Connecting to $($site.AbsoluteUri)"
if ($PSCmdlet.ShouldProcess($site.AbsoluteUri, "Connect PnP")) {
Connect-PnPOnline -Url $site.AbsoluteUri -ClientId $ClientId -ErrorAction Stop
}
try {
$app = Resolve-AppFromTenantCatalog -Title $AppTitle
Write-Verbose "Resolved app '$($app.Title)' (Id: $($app.Id)) from Tenant App Catalog."
Ensure-AppInstalledToSite -AppId ([guid]$app.Id)
$processed++
Write-Information "Installed (or already present) '$($app.Title)' in $($site.AbsoluteUri)"
}
finally {
Write-Verbose "Disconnecting from $($site.AbsoluteUri)"
Disconnect-PnPOnline -ErrorAction SilentlyContinue
}
}
catch {
$failed++
Write-Warning "Failed for site $($site.AbsoluteUri): $($_.Exception.Message) : $_.Exception"
Disconnect-PnPOnline -ErrorAction SilentlyContinue
}
}
Write-Information "Finished. Processed=$processed; Failed=$failed"