public/Get-PnPFoldersItems.ps1

<#
 # Created by Ryen Kia Zhi Tang
 #>


function Get-PnPFoldersItems
{
    [CmdletBinding()]

    param
    (
        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True,
            ParameterSetName='Connect')] 
        [Alias('uri')]
        [Uri] $Url,

        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True)] 
        [Alias('path')]
        [String] $FolderSiteRelativeUrl,

        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True)] 
        [Alias('dest')]
        [String] $Destination,

        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True)] 
        [String[]] $ExcludeFileExtension,

        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True)] 
        [String[]] $ExcludeFolderSiteRelativeUrl,

        [Parameter( 
            Mandatory=$False, 
            ValueFromPipeline=$True, 
            ValueFromPipelineByPropertyName=$True,
            ParameterSetName='Connect')]
        [SharePointPnP.PowerShell.Commands.Base.PipeBinds.CredentialPipeBind] $Credential
    )

    begin
    {
        $ProgressCounter = 0

        if(!(Get-Module `
                -Name 'SharePointPnPPowerShellOnline' `
                -ListAvailable) `
            -and `
            !(Get-Module `
                -Name 'SharePointPnPPowerShell2013' `
                -ListAvailable) `
            -and `
            !(Get-Module `
                -Name 'SharePointPnPPowerShell2016' `
                -ListAvailable))
        {
            Write-Warning `
                -Message `
                    ([String]::Format('"{0}" {1} "{2}" {3}',
                        'Get-PnPFoldersItems',
                        'cmdlet requires',
                        'SharePointPnPPowerShellOnline or SharePointPnPPowerShell2013 or SharePointPnPPowerShell2016',
                        'SharePoint Online PowerShell Module to be installed.')) ;

            Write-Warning `
                -Message `
                    ([String]::Format('{0} "{1}" {2}: {3}',
                        'Please kindly install the',
                        'SharePointPnPPowerShellOnline or SharePointPnPPowerShell2013 or SharePointPnPPowerShell2016',
                        'SharePoint PowerShell Module using the following command',
                        'Install-Module -Name SharePointPnPPowerShellVERSION')) ;

            Break ;
        }
        #rely on auto load module instead of importing explicitly

        if($Credential -ne (Out-Null))
        {
            
            try
            {
                Connect-PnPOnline `
                    -Url $Url.AbsoluteUri `
                    -Credentials $Credential ;
                
                $Connection = Get-PnPConnection ;
            }
            catch [System.Exception]
            {
                throw($_) ;
            }
        }
        else
        {
            try
            {
                $Connection = Get-PnPConnection ;
            }
            catch [System.Exception]
            {
                throw($_) ;
            }
        }

    }

    process
    {
        $Items = @(Get-PnPFolderItem `
            -FolderSiteRelativeUrl $FolderSiteRelativeUrl)

        foreach ($Item in $Items)
        {

            # Strip the Site URL off the item path, because Get-PnpFolderItem wants it
            # to be relative to the site, not an absolute path.
            $ItemPath = $Item.ServerRelativeUrl `
                -replace "^$(([Uri]$Item.Context.Url).AbsolutePath)/",'' ;
        
            $DestinationFolderPath = [String]::Format('{0}\{1}',
                $Destination,
                ((Split-Path $ItemPath).Replace('/','\'))) ;
                
            if($ExcludeFolderSiteRelativeUrl `
                -notcontains `
                (Split-Path $ItemPath -Parent).Replace('\','/'))
            {

                # If this is a directory, recurse into this function.
                # Otherwise, write the item out to the pipeline.
                if ($Item.GetType().Name -eq 'Folder')
                {
                    if($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Destination"))
                    {
                        Get-PnPFoldersItems `
                            -FolderSiteRelativeUrl $ItemPath `
                            -Destination $Destination `
                            -ExcludeFileExtension $ExcludeFileExtension `
                            -ExcludeFolderSiteRelativeUrl $ExcludeFolderSiteRelativeUrl ;
                    }
                    else
                    {
                        Get-PnPFoldersItems `
                            -FolderSiteRelativeUrl $ItemPath `
                            -ExcludeFileExtension $ExcludeFileExtension `
                            -ExcludeFolderSiteRelativeUrl $ExcludeFolderSiteRelativeUrl ;
                    }
                }
                else 
                {
                    if($ExcludeFileExtension `
                        -notcontains `
                        [IO.Path]::GetExtension($(Split-Path -Path $ItemPath -Leaf)))
                    {
                        if($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("Destination"))
                        {
                            if(!(Test-Path $DestinationFolderPath))
                            {
                                Write-Warning `
                                    -Message `
                                        ([String]::Format('{0} [{1}] {2}',
                                            'Folder path',
                                            $DestinationFolderPath,
                                            'does not exist')) ;

                                try
                                {
                                    New-Item `
                                        -Path $DestinationFolderPath `
                                        -ItemType Directory `
                                        -Force | `
                                            Out-Null ;
                    
                                    Write-Host `
                                        -Object `
                                            ([String]::Format('{0} [{1}] {2}',
                                                'Created',
                                                $DestinationFolderPath,
                                                'folder path')) `
                                        -ForegroundColor Green ; ;
                                }
                                catch [System.IO.IOException]
                                { 
                                    throw($_) ; 
                                }
                            }
                        
                            try
                            {
                                Write-Verbose `
                                    -Message `
                                        ([String]::Format('{0} [{1}] {2} [{3}]',
                                            'Downloading',
                                            $Item.ServerRelativeUrl, `
                                            'from', `
                                            $FolderSiteRelativeUrl)) ;

                                Write-Progress `
                                    -Activity `
                                        ([String]::Format('{0} [{1}] {2} [{3}]',
                                            'Downloading',
                                            $Item.ServerRelativeUrl, `
                                            'from', `
                                            $FolderSiteRelativeUrl)) `
                                    -Status `
                                        ([String]::Format('{0}: {1} {2} {3}',
                                            'Downloading',
                                            $ProgressCounter,
                                            'of',
                                            $($Items.Count))) `
                                    -PercentComplete (($ProgressCounter / $Items.Count)  * 100) ;
                            
                                Get-PnPFile `
                                    -Url $Item.ServerRelativeUrl `
                                    -Path $DestinationFolderPath `
                                    -AsFile `
                                    -Force ;

                                Write-Verbose `
                                    -Message `
                                        ([String]::Format('{0} [{1}] {2} [{3}]',
                                            'Saving',
                                            $Item.ServerRelativeUrl, `
                                            'to', `
                                            $DestinationFolderPath)) ;

                                $ProgressCounter++ ;
                            }
                            catch [Microsoft.SharePoint.Client.ClientRequestException]
                            { 
                                throw($_) ;
                            }
                        }
                        else
                        {
                            [Microsoft.SharePoint.Client.File] $File = $Item ;

                            Write-Output `
                                -InputObject `
                                    (New-Object `
                                        -TypeName PSObject `
                                        -Property ([Ordered] `
                                            @{
                                                Name = $File.Name
                                                Type = 'File'
                                                Path = (Split-Path $File.ServerRelativeUrl)
                                                Length = $File.Length
                                                Created = $File.TimeCreated
                                                Modified = $File.TimeLastModified
                                            }
                                        )
                                    );
                        }
                    }
                }
            }
        }
    }
    
    end
    {
    }
}
# SIG # Begin signature block
# MIIQ/wYJKoZIhvcNAQcCoIIQ8DCCEOwCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUqqr+DvGWyxRi8GrCBv4cJdb9
# 22SgggyRMIID7jCCA1egAwIBAgIQfpPr+3zGTlnqS5p31Ab8OzANBgkqhkiG9w0B
# AQUFADCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIG
# A1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhh
# d3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcg
# Q0EwHhcNMTIxMjIxMDAwMDAwWhcNMjAxMjMwMjM1OTU5WjBeMQswCQYDVQQGEwJV
# UzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xMDAuBgNVBAMTJ1N5bWFu
# dGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgQ0EgLSBHMjCCASIwDQYJKoZIhvcN
# AQEBBQADggEPADCCAQoCggEBALGss0lUS5ccEgrYJXmRIlcqb9y4JsRDc2vCvy5Q
# WvsUwnaOQwElQ7Sh4kX06Ld7w3TMIte0lAAC903tv7S3RCRrzV9FO9FEzkMScxeC
# i2m0K8uZHqxyGyZNcR+xMd37UWECU6aq9UksBXhFpS+JzueZ5/6M4lc/PcaS3Er4
# ezPkeQr78HWIQZz/xQNRmarXbJ+TaYdlKYOFwmAUxMjJOxTawIHwHw103pIiq8r3
# +3R8J+b3Sht/p8OeLa6K6qbmqicWfWH3mHERvOJQoUvlXfrlDqcsn6plINPYlujI
# fKVOSET/GeJEB5IL12iEgF1qeGRFzWBGflTBE3zFefHJwXECAwEAAaOB+jCB9zAd
# BgNVHQ4EFgQUX5r1blzMzHSa1N197z/b7EyALt0wMgYIKwYBBQUHAQEEJjAkMCIG
# CCsGAQUFBzABhhZodHRwOi8vb2NzcC50aGF3dGUuY29tMBIGA1UdEwEB/wQIMAYB
# Af8CAQAwPwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NybC50aGF3dGUuY29tL1Ro
# YXd0ZVRpbWVzdGFtcGluZ0NBLmNybDATBgNVHSUEDDAKBggrBgEFBQcDCDAOBgNV
# HQ8BAf8EBAMCAQYwKAYDVR0RBCEwH6QdMBsxGTAXBgNVBAMTEFRpbWVTdGFtcC0y
# MDQ4LTEwDQYJKoZIhvcNAQEFBQADgYEAAwmbj3nvf1kwqu9otfrjCR27T4IGXTdf
# plKfFo3qHJIJRG71betYfDDo+WmNI3MLEm9Hqa45EfgqsZuwGsOO61mWAK3ODE2y
# 0DGmCFwqevzieh1XTKhlGOl5QGIllm7HxzdqgyEIjkHq3dlXPx13SYcqFgZepjhq
# IhKjURmDfrYwggP0MIIC3KADAgECAhB/a5fIdzrerUEQmV6aj/9NMA0GCSqGSIb3
# DQEBCwUAMBwxGjAYBgNVBAMMEVJ5ZW4uS2lhLlpoaS5UYW5nMB4XDTE3MTIyNjA5
# NTE1NVoXDTE4MTIyNjEwMTE1NVowHDEaMBgGA1UEAwwRUnllbi5LaWEuWmhpLlRh
# bmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDsBt37XrwrkReIAsf4
# Z6Ij9Du29Q/YLq1djViZvCfbWBwWSU+mYQs65IX58qg6D/kzX+QAdN7BrYkutk49
# Heqat7+9c1bDn8C1MtJs4D7xbPX2TrhvZJ4aFpSE05BXd9xI1NqYYGON32lVDilI
# +6yiD9/GfZhej0ysUPNHBsr0hq1TxHfILjmf8K2draYack0tr3gfOgPRrrgF+khZ
# Um1pS1S9e07OkWCH3L+O9y4x/1rapp9+d1kx5iF6zD3NHvitnIuNSV70livhr0B8
# V9GZsZ5Ln8QfhpZ68oEAK5ud/kTnK6sWkea2kV5eQNT/KNSm7+zfJ0bmIUvIDDtm
# 4q+tAgMBAAGjggEwMIIBLDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYB
# BQUHAwMwgeUGA1UdEQSB3TCB2oY1aHR0cHM6Ly9tdnAubWljcm9zb2Z0LmNvbS9l
# bi11cy9QdWJsaWNQcm9maWxlLzUwMDE3MTCGRWh0dHBzOi8vc29jaWFsLnRlY2hu
# ZXQubWljcm9zb2Z0LmNvbS9wcm9maWxlL3J5ZW4lMjBraWElMjB6aGklMjB0YW5n
# L4YjaHR0cHM6Ly9uei5saW5rZWRpbi5jb20vaW4vcnllbnRhbmeGGmh0dHBzOi8v
# dHdpdHRlci5jb20va2lhemhphhlodHRwczovL2dpdGh1Yi5jb20va2lhemhpMB0G
# A1UdDgQWBBSGIqBWna8/GZNMsH+T5JM8jmkeNjANBgkqhkiG9w0BAQsFAAOCAQEA
# b/lIFMuGkQYH1mMdAXYBfgHZKq85vayddmoXJcXIzlwFygBTus9oytgln1nG1y20
# S7Wvb5a2Mmo6hyzIX1W8xB0mznW9EKI35dSfCzY4AJnpZFyguRn+JwumQJWN++Ej
# 4qp3tRQeJ2v0/Nsm8Q1Amp03S4oWZ1Ro5NRbpOILbk/IMRuZN4kecxltpyb7XKPG
# +GESKe4sGqJny3NRjGNdVE2CH/cJhsCzJdwgQwED8FVS/h/k4gkURdOJTQR8fOxI
# fMVtR69W3PZ3FEnFaN0frfevpImNRD5ucJd3Bp+NiJfK9DxKvgudiIth92okpP5w
# 7TYgNQKPDV59EFC5WUs6hjCCBKMwggOLoAMCAQICEA7P9DjI/r81bgTYapgbGlAw
# DQYJKoZIhvcNAQEFBQAwXjELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5bWFudGVj
# IENvcnBvcmF0aW9uMTAwLgYDVQQDEydTeW1hbnRlYyBUaW1lIFN0YW1waW5nIFNl
# cnZpY2VzIENBIC0gRzIwHhcNMTIxMDE4MDAwMDAwWhcNMjAxMjI5MjM1OTU5WjBi
# MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xNDAy
# BgNVBAMTK1N5bWFudGVjIFRpbWUgU3RhbXBpbmcgU2VydmljZXMgU2lnbmVyIC0g
# RzQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiYws5RLi7I6dESbsO
# /6HwYQpTk7CY260sD0rFbv+GPFNVDxXOBD8r/amWltm+YXkLW8lMhnbl4ENLIpXu
# witDwZ/YaLSOQE/uhTi5EcUj8mRY8BUyb05Xoa6IpALXKh7NS+HdY9UXiTJbsF6Z
# WqidKFAOF+6W22E7RVEdzxJWC5JH/Kuu9mY9R6xwcueS51/NELnEg2SUGb0lgOHo
# 0iKl0LoCeqF3k1tlw+4XdLxBhircCEyMkoyRLZ53RB9o1qh0d9sOWzKLVoszvdlj
# yEmdOsXF6jML0vGjG/SLvtmzV4s73gSneiKyJK4ux3DFvk6DJgj7C72pT5kI4RAo
# cqrNAgMBAAGjggFXMIIBUzAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG
# AQUFBwMIMA4GA1UdDwEB/wQEAwIHgDBzBggrBgEFBQcBAQRnMGUwKgYIKwYBBQUH
# MAGGHmh0dHA6Ly90cy1vY3NwLndzLnN5bWFudGVjLmNvbTA3BggrBgEFBQcwAoYr
# aHR0cDovL3RzLWFpYS53cy5zeW1hbnRlYy5jb20vdHNzLWNhLWcyLmNlcjA8BgNV
# HR8ENTAzMDGgL6AthitodHRwOi8vdHMtY3JsLndzLnN5bWFudGVjLmNvbS90c3Mt
# Y2EtZzIuY3JsMCgGA1UdEQQhMB+kHTAbMRkwFwYDVQQDExBUaW1lU3RhbXAtMjA0
# OC0yMB0GA1UdDgQWBBRGxmmjDkoUHtVM2lJjFz9eNrwN5jAfBgNVHSMEGDAWgBRf
# mvVuXMzMdJrU3X3vP9vsTIAu3TANBgkqhkiG9w0BAQUFAAOCAQEAeDu0kSoATPCP
# YjA3eKOEJwdvGLLeJdyg1JQDqoZOJZ+aQAMc3c7jecshaAbatjK0bb/0LCZjM+RJ
# ZG0N5sNnDvcFpDVsfIkWxumy37Lp3SDGcQ/NlXTctlzevTcfQ3jmeLXNKAQgo6rx
# S8SIKZEOgNER/N1cdm5PXg5FRkFuDbDqOJqxOtoJcRD8HHm0gHusafT9nLYMFivx
# f1sJPZtb4hbKE4FtAC44DagpjyzhsvRaqQGvFZwsL0kb2yK7w/54lFHDhrGCiF3w
# PbRRoXkzKy57udwgCRNx62oZW8/opTBXLIlJP7nPf8m/PiJoY1OavWl0rMUdPH+S
# 4MO8HNgEdTGCA9gwggPUAgEBMDAwHDEaMBgGA1UEAwwRUnllbi5LaWEuWmhpLlRh
# bmcCEH9rl8h3Ot6tQRCZXpqP/00wCQYFKw4DAhoFAKBwMBAGCisGAQQBgjcCAQwx
# AjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAM
# BgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBTj3zkxxggwscroc5xtMppERh6e
# uTANBgkqhkiG9w0BAQEFAASCAQCYgjoFfu538vNWyVf7DmiNJdZm2iGWeVi84Ex8
# SB0Ia2V2B/VJCOinHiqHFw0GDhq64P3WQwDQVHtX4B2cBiMbHrWFUemuOEeGPPWa
# uL8DvUUNDzEOnKY8GiAssKSEdhUtwfGhsSZYcqlb93fsfU87coKwxIyys2TkcuqW
# 1bwSJqpTdsCqnlLgXwVMzT739dJ6tEMDDkI2ILUxvK4oWSworFR4Nt437Z/ZOjCQ
# yIt0Jzyibzcz815td3OK+QoGP4PFucrD6lK0xY4oamW6+Ib0tABMMj8ompf2EzeM
# kPW3xcY9onNu8x11uWpmcQGAUeQMTxFFdm8jqzcrX5oxY2uEoYICCzCCAgcGCSqG
# SIb3DQEJBjGCAfgwggH0AgEBMHIwXjELMAkGA1UEBhMCVVMxHTAbBgNVBAoTFFN5
# bWFudGVjIENvcnBvcmF0aW9uMTAwLgYDVQQDEydTeW1hbnRlYyBUaW1lIFN0YW1w
# aW5nIFNlcnZpY2VzIENBIC0gRzICEA7P9DjI/r81bgTYapgbGlAwCQYFKw4DAhoF
# AKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4
# MDcxMTAwMjAwNVowIwYJKoZIhvcNAQkEMRYEFAcAEJNS+fhsMIM43W7oDhaENkiN
# MA0GCSqGSIb3DQEBAQUABIIBAD/UwtpSmeVXtyBwc8HiAgxuofbz+ikzZCydIVQd
# a+UnNFFi5ODOCD1MPAin9xBVxD57SZmxneN5gAgleG9bH7Ez2ErzRdi0fAQ2NC9u
# hbiS4iQvxMPx4IISmZ17iQHx6L7fQloBZGcKNtU08ttRuYo3BHb3Z9i5zLC5sOZJ
# ShzYfz4TWe4wOEEk9m4rycq7YtBfpS0f1/Hpbm9x/9yg2QAA7vLnLqd5g6eTm1kj
# pv+S83hJE7OZrQ1rbw9lWZ0DeEjuMIpFhoWhueav9Q1w7GeKJtEWWyjGGpobLQWp
# CyKoDzs/x1CTN9Ue/um/jrVL0ud+iYSy7mUBQqdmy/R9zs4=
# SIG # End signature block