Functions/CatalogItems/Out-RsFolderContent.ps1

# Copyright (c) 2016 Microsoft Corporation. All Rights Reserved.
# Licensed under the MIT License (MIT)


function Out-RsFolderContent
{
    <#
        .SYNOPSIS
            This downloads catalog items from a folder to disk
        
        .DESCRIPTION
            This downloads catalog items from a folder server to disk.
            Currently the script only downloads reports, datasources, datasets and resources.
        
        .PARAMETER Recurse
            Recursively download subfolders.
        
        .PARAMETER RsFolder
            Path to folder on report server to download catalog items from.
        
        .PARAMETER Destination
            Folder to download catalog items to.
    
        .PARAMETER ReportServerUri
            Specify the Report Server URL to your SQL Server Reporting Services Instance.
            Use the "Connect-RsReportServer" function to set/update a default value.
        
        .PARAMETER Credential
            Specify the credentials to use when connecting to the Report Server.
            Use the "Connect-RsReportServer" function to set/update a default value.
        
        .PARAMETER Proxy
            Report server proxy to use.
            Use "New-RsWebServiceProxy" to generate a proxy object for reuse.
            Useful when repeatedly having to connect to multiple different Report Server.
        
        .EXAMPLE
            Out-RsFolderContent -ReportServerUri 'http://localhost/reportserver_sql2012' -RsFolder /MonthlyReports -Destination C:\reports\MonthlyReports
            
            Description
            -----------
            Downloads catalogitems from /MonthlyReports into folder C:\reports\MonthlyReports
    #>

    [CmdletBinding()]
    param(
        [switch]
        $Recurse,
        
        [Alias('ItemPath', 'Path')]
        [Parameter(Mandatory = $True)]
        [string]
        $RsFolder,
        
        [ValidateScript({ Test-Path $_ -PathType Container })]
        [Parameter(Mandatory = $True)]
        [string]
        $Destination,
        
        [string]
        $ReportServerUri,
        
        [Alias('ReportServerCredentials')]
        [System.Management.Automation.PSCredential]
        $Credential,
        
        $Proxy
    )
    
    $Proxy = New-RsWebServiceProxyHelper -BoundParameters $PSBoundParameters
    
    $GetRsFolderContentParam = @{
        Proxy = $Proxy
        RsFolder = $RsFolder
        Recurse = $Recurse
        ErrorAction = 'Stop'
    }
    
    try
    {
        $items = Get-RsFolderContent @GetRsFolderContentParam
    }
    catch
    {
        throw (New-Object System.Exception("Failed to retrieve items in '$RsFolder': $($_.Exception.Message)", $_.Exception))
    }
    
    $Destination = Convert-Path $Destination

    ## Loop for folders first, because we need to ensure folders are written to disk before we
    ## attempt to write any files. Otherwise, file writes will randomly fail to write because
    ## its folder has not been created yet.
    ## The Solution was to loop and create all folders then work on the files.
    ## Basically, create all folders, then write all the files.
    foreach ($item in $items)
    {
        if (($item.TypeName -eq 'Folder') -and $Recurse)
        {
            $relativePath = $item.Path
            if($RsFolder -ne "/")
            {
                $relativePath = Clear-Substring -string $relativePath -substring $RsFolder -position front
            }
            $relativePath = $relativePath.Replace("/", "\")
            
            $newFolder = $Destination + $relativePath
            Write-Verbose "Creating folder $newFolder"
            New-Item $newFolder -ItemType Directory -Force | Out-Null
            Write-Verbose "Folder: $newFolder was created successfully."
        }
    } ## End Folder Loop
    
   ## Loop for non-folders.
   foreach ($item in $items)
   {     
        if ($item.TypeName -eq "Resource" -or 
            $item.TypeName -eq "Report" -or 
            $item.TypeName -eq "DataSource" -or 
            $item.TypeName -eq "DataSet"   -or 
            $item.TypeName -eq "Component")
        {
            # TODO: REMOVE this comment below. This is the comment that caused me to look for a solution.
            # We're relying on the fact that the implementation of Get-RsFolderContent will show us the folder before their content,
            # when using the -recurse option, so we can assume that any subfolder will be created before we download the items it contains
            $relativePath = $item.Path
            if($RsFolder -ne "/")
            {
                $relativePath = Clear-Substring -string $relativePath -substring $RsFolder -position front
            }
            $relativePath = Clear-Substring -string $relativePath -substring ("/" + $item.Name) -position back
            $relativePath = $relativePath.replace("/", "\")

            $folder = $Destination + $relativePath
            Out-RsCatalogItem -proxy $proxy -RsFolder $item.Path -Destination $folder
        }
    } ## End non-Folder loop
}