Skip to content

Instantly share code, notes, and snippets.

@michaelmaillot
Created April 19, 2022 22:00

Revisions

  1. michaelmaillot created this gist Apr 19, 2022.
    200 changes: 200 additions & 0 deletions deploy-spfx-pnp-powershell.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,200 @@
    name: deploy with pnp powershell

    on:
    workflow_call:
    inputs:
    environment_name:
    description: 'Target environment to deploy to'
    required: true
    type: string
    site_url_prd:
    description: 'SharePoint PRD site URL'
    required: true
    type: string
    site_list_prd:
    description: 'SharePoint PRD list relative URL ("Lists/MyList")'
    default: ""
    required: false
    type: string
    site_copy_owner:
    description: 'SharePoint copy site owner to be'
    default: ""
    required: false
    type: string
    webpart_name:
    description: 'SPFx WebPart name to add to test page'
    default: ""
    required: false
    type: string
    site_to_remove:
    description: 'SharePoint copy site url to remove'
    default: ""
    required: false
    type: string
    app_catalog_site_url:
    description: 'SharePoint app catalog site URL (tenant or site collection)'
    default: ""
    required: false
    type: string
    app_catalog_scope:
    description: 'Indicates the PRD app catalog scope (tenant or site collection)'
    default: ""
    required: false
    type: string
    outputs:
    site_to_remove:
    description: 'SharePoint copy site url to remove'
    value: ${{ jobs.deploy.outputs.site_copy_url }}
    secrets:
    AAD_APP_ID:
    required: true
    AAD_APP_PASSWORD:
    required: true
    AAD_APP_ENCODED_CERTIFICATE:
    required: true
    AAD_TENANT_ID:
    required: true

    jobs:
    deploy:
    runs-on: windows-2022
    environment: ${{ inputs.environment_name }}
    outputs:
    site_copy_url: ${{ steps.step_site_copy.outputs.url }}
    steps:
    - name: Download artifact from build job
    uses: actions/download-artifact@v2

    - name: Installing PnP.PowerShell Module
    shell: pwsh
    run: Install-Module -Name "PnP.PowerShell" -Force

    - name: Get generated *.sppkg filename
    id: package
    shell: pwsh
    run: |
    $package = Get-ChildItem -Path artifact -Recurse -Filter '*.sppkg' | Select Name | Select-Object -First 1
    echo "::set-output name=name::$($package.Name)"
    - name: Connecting to Production site and get site template
    if: ${{ inputs.environment_name != 'PRD' }}
    shell: pwsh
    run: |
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ inputs.site_url_prd }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    Get-PnPSiteTemplate -Out template.pnp
    Add-PnPDataRowsToSiteTemplate -Path template.pnp -List "${{ inputs.site_list_prd }}"
    - name: Create ${{ inputs.environment_name }} site copy
    if: ${{ inputs.environment_name != 'PRD' }}
    id: step_site_copy
    shell: pwsh
    run: |
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ inputs.site_url_prd }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    $web = Get-PnPWeb
    $siteName = "$($web.Title)" + [guid]::NewGuid().Guid
    $uri = [System.Uri]"${{ inputs.site_url_prd }}"
    $siteUrl = $uri.Scheme + "://" + $uri.Authority + "/sites/" + $siteName
    $site = New-PnPSite -Type CommunicationSite -Title $siteName -Url $siteUrl -Owner "${{ inputs.site_copy_owner }}" -Wait
    Write-Host "::set-output name=url::$site"
    Write-Host $site
    - name: Apply PRD site template to ${{ inputs.environment_name }} site copy
    if: ${{ inputs.environment_name != 'PRD' }}
    shell: pwsh
    run: |
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ steps.step_site_copy.outputs.url }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    $applyTemplateHasFailed = $false
    try {
    Invoke-PnPSiteTemplate -Path template.pnp -ClearNavigation
    }
    catch {
    Write-Host $_.Exception.Message -ForegroundColor Yellow
    $applyTemplateHasFailed = $_.Exception.Message -Match "HttpClient.Timeout"
    }
    finally {
    if ($applyTemplateHasFailed -eq $true) {
    Write-Host "Retrying to apply PRD site template (after 30 seconds delay)"
    Start-Sleep -Seconds 30
    Invoke-PnPSiteTemplate -Path template.pnp -ClearNavigation
    }
    }
    - name: Enable Site Collection App Catalog, upload SharePoint package, deploy it and add it to the site
    if: ${{ inputs.environment_name != 'PRD' }}
    shell: pwsh
    run: |
    $uploadHasFailed = $false
    try {
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ steps.step_site_copy.outputs.url }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    Add-PnPSiteCollectionAppCatalog
    Add-PnPApp -Path "artifact/${{ steps.package.outputs.name }}" -Scope Site -Publish
    }
    catch {
    Write-Host $_.Exception.Message -ForegroundColor Yellow
    Write-Host "Retrying by removing and re-enabling the site collection app catalog, then the package (after 30 seconds delay)"
    $uploadHasFailed = $true
    }
    finally {
    if ($uploadHasFailed -eq $true) {
    Remove-PnPSiteCollectionAppCatalog -Site ${{ steps.step_site_copy.outputs.url }}
    Add-PnPSiteCollectionAppCatalog
    Start-Sleep -Seconds 30
    Add-PnPApp -Path "artifact/${{ steps.package.outputs.name }}" -Scope Site -Publish -Overwrite
    }
    }
    # Enabling the site collection app catalog or deploying a solution on it can fail, as the site has just been created
    # We prevent this failure, by waiting and retrying those actions

    - name: Add an article page and the WebPart (if exists)
    if: ${{ inputs.environment_name != 'PRD' }}
    shell: pwsh
    run: |
    $webPartHasFailed = $false
    if ('${{ inputs.webpart_name }}' -ne '') {
    try {
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ steps.step_site_copy.outputs.url }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    Add-PnPPage -Name "TestSPFx"
    Add-PnPPageWebPart -Page "TestSPFx" -Component "${{ inputs.webpart_name }}"
    }
    catch {
    Write-Host $_.Exception.Message -ForegroundColor Yellow
    Write-Host "Retrying the WebPart addition after 30 seconds delay"
    $webPartHasFailed = $true
    Start-Sleep -Seconds 30
    }
    finally {
    if ($webPartHasFailed -eq $true) {
    Add-PnPPageWebPart -Page "TestSPFx" -Component "${{ inputs.webpart_name }}"
    }
    }
    }
    # Here again, adding a page with the WebPart ont it can fail, as the site has just been created
    # We prevent this failure, by waiting and retrying those actions

    - name: Upload SharePoint package to ${{ inputs.app_catalog_scope }} App Catalog (PRD)
    if: ${{ inputs.environment_name == 'PRD' }}
    run: |
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ inputs.site_url_prd }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    Add-PnPApp -Path "artifact/${{ steps.package.outputs.name }}" -Scope ${{ inputs.app_catalog_scope }} -Publish -Overwrite
    - name: Remove site copy (PRD)
    if: ${{ inputs.environment_name == 'PRD' && inputs.site_to_remove != '' }}
    run: |
    $securePassword = ConvertTo-SecureString "${{ secrets.AAD_APP_PASSWORD }}" -AsPlainText -Force
    Connect-PnPOnline -Url ${{ inputs.site_url_prd }} -CertificateBase64Encoded ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }} -CertificatePassword $securePassword -ClientId ${{ secrets.AAD_APP_ID }} -Tenant ${{ secrets.AAD_TENANT_ID }}
    Remove-PnPTenantSite -Url ${{ inputs.site_to_remove }} -SkipRecyclebin -Force
    38 changes: 38 additions & 0 deletions main.yml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    name: main

    on:
    push:
    branches:
    - main
    workflow_dispatch: # Can be triggered manually

    jobs:
    build:
    uses: michaelmaillot/helloworld-copysite/.github/workflows/build-spfx.yml@main
    deploy_uat:
    needs: build
    uses: michaelmaillot/helloworld-copysite/.github/workflows/deploy-spfx-pnp-powershell-copy.yml@main
    with:
    environment_name: UAT
    site_url_prd: https://onepointdev365.sharepoint.com/sites/CommSite
    site_list_prd: "Lists/EmployeeOnboarding"
    site_copy_owner: michael.maillot@onepointdev365.onmicrosoft.com
    webpart_name: "HelloWorld"
    secrets:
    AAD_APP_ID: ${{ secrets.AAD_APP_ID }}
    AAD_APP_PASSWORD: ${{ secrets.AAD_APP_PASSWORD }}
    AAD_APP_ENCODED_CERTIFICATE: ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }}
    AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }}
    deploy_prd:
    needs: deploy_uat
    uses: michaelmaillot/helloworld-copysite/.github/workflows/deploy-spfx-pnp-powershell-copy.yml@main
    with:
    environment_name: PRD
    site_url_prd: https://onepointdev365.sharepoint.com/sites/CommSite
    app_catalog_scope: tenant
    site_to_remove: "${{ needs.deploy_uat.outputs.site_to_remove }}"
    secrets:
    AAD_APP_ID: ${{ secrets.AAD_APP_ID }}
    AAD_APP_PASSWORD: ${{ secrets.AAD_APP_PASSWORD }}
    AAD_APP_ENCODED_CERTIFICATE: ${{ secrets.AAD_APP_ENCODED_CERTIFICATE }}
    AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }}