Wednesday, June 15, 2016

Powershell Script - New Version of my SSRS Bulk Upload

This Powershell script takes all report files(.rdl) in a folder ,creates the folder structure on the server ,uploads the reports and changes the data sources.

If you liked the first version of this script ,you'll love the new version.
The first version was written to solve a problem and nothing more, but turned out that it was something the  SSRS community was waiting for as it is one of the most read posts on my blog.

The new version has been completely rewritten to make it more robust and in the process i've added a feature or two.
  • It now uses the Reporting Service 2010 Web Service
  • It's now structurally a better script. Easier to understand and work with.
  • Supports Folder structure
  • Can easily be modified to upload all kinds of elements like images,pdf+
The script is designed for Configmgr reports ,but the only part that's specific to Configmgr is the part that searches for the data source. To work with other data sources you just need to modify the search criteria or just "hard code" in the name of the shared data source you want to use.

How It works

The new script has default values on some of the parameters to make it easier to use.
only required parameter is the URL(-webserviceUrl) to the reporting server.

& '.\Upload Reports v3.ps1' -webServiceUrl http://yourSSRSserver

Default it will look in the script folder for reports ,upload them to a folder called "Larsen Reports" and wont overwrite if report already exist ,but all this can be changed by setting additional parameters.

  & '.\Upload Reports v3.ps1' -webServiceUrl http://yourSSRSserver -SourceDirectory d\Reports  -ReportFolder Demo -overwrite $true


In this example i just threw a few reports in a folder/sub folder structure. Some reports on the root and some in the sub directories.


I run the script assigning the -reportFolder paramenter to Demo.

As you can see in the screenshot under it first creates the folder structure, then it searches for *.rdl files and upload them to the correct folder/ sub folder and changes the data source.

This powershell script takes all report files(.rdl) ,creates the folder structure on the server ,uploads the reports and changes the datasources.

Everything should be ready to use!

Looking at the Report server, it has created the folders Demo and the 3 sub folders. The reports are uploaded in the right place.



..and the data source has been changed to the correct one.



Download: Upload Reports v3.zip

<#
.SYNOPSIS
    Bulk import SSRS Reports from a folder and update the reports data source.
.DESCRIPTION
    This script takes all the RDL files in a folder and imports them into a SSRS and updates the reports data source to the Configmgr shared data source.
.NOTES
    File Name  : Upload Reports.ps1
    Version    : 3.00 ,13.jun-2016
    Author     : Thomas Larsen - thomas.larsen@tromsfylke.no
    Requires   : Version 3
.LINK
    Blog
      http://larsenconfigmgr.blogspot.com

.PARAMETER webServiceUrl
Base URL to the report server ,usually the Configmgr Site server

.PARAMETER reportFolder
 Report Server folder where the reports are imported to, the script creates the folder if it's not there.
 
.PARAMETER SourceDirectory
The local folder where the .rdl files are located.

.PARAMETER Overwrite
Set to True if Script should overwrite Reports it they already exist.

.Example

& '.\Upload Reports.ps1' -webServiceUrl "http://Reportserver.domain.local"
Takes the report files from current directory(And Subdirectories) and uploads them the to a folder on SSRS called Larsen Reports(Default)

.Example

& '.\Upload Reports.ps1' -webServiceUrl "http://Reportserver.domain.local" -reportFolder "Larsen Reports -SourceDirectory "c:\ReportsToUpload" -overwrite $True
All Parameters ,select Sourcefolder Destination Folder ,Overwrite if exist


#>

Param(
   [string]$webServiceUrl,
      
   [string]$reportFolder = "Larsen Reports",

   [string]$SourceDirectory = $PSScriptRoot,

   [bool]$overwrite = $false
   )

Write-Host "Thomas Larsen - December 2015 - http://larsenconfigmgr.blogspot.com" -ForegroundColor Cyan
Write-Host "This Script is provided AS-IS, no warrenty is provided" 
Write-host ""

#Connect to SSRS
Write-Host "Reportserver: $webServiceUrl" -ForegroundColor DarkMagenta
Write-Host "Estabishing Proxy connection, connecting to : $webServiceUrl/ReportServer/ReportService2010.asmx?WSDL"
Write-Host ""

$ssrsProxy = New-WebServiceProxy -Uri $webServiceUrl'/ReportServer/ReportService2010.asmx?WSDL' -UseDefaultCredential

#######
#Get Configmgr shared datasource
$Items = $ssrsProxy.listchildren("/", $true) | where {$_.TypeName -eq "Datasource" }
foreach ($item in $items)
    {
    #Check to see if Datasource name patches Guid Pattern
    if ($item.name -match '{([a-zA-Z0-9]{8})-([a-zA-Z0-9]{4})-([a-zA-Z0-9]{4})-([a-zA-Z0-9]{4})-([a-zA-Z0-9]{12})}' -and $item.path -like '/ConfigMgr*')
        {
        Write-Host "Datasource:" $item.Name -ForegroundColor Magenta  
        Write-host "Type:" $item.TypeName 
        Write-Host "Path:" $item.Path

        #Save parameters for later use.
        $DatasourceName = $item.Name
        $DatasourcePath = $item.Path
        }
    }

function SSRSFolder ([string]$reportFolder,[string]$reportPath)
{
##########################################    
#Create Folder     
        try
        {
            $ssrsProxy.CreateFolder($reportFolder, $reportPath, $null) | out-null
            if($reportPath -eq '/')
            {
            Write-Host "Folder `"$reportpath$reportFolder`" Created"
            }
            else
            {
            Write-Host "Folder `"$reportpath/$reportFolder`" Created"
            }
        }
        catch [System.Web.Services.Protocols.SoapException]
        {
            if ($_.Exception.Detail.InnerText -match "rsItemAlreadyExists400")
            {
                Write-Host "Folder: $reportFolder already exists."
            }
            else
            {
                $msg = "Error creating folder: $reportFolder. Msg: '{0}'" -f $_.Exception.Detail.InnerText
                Write-Error $msg
            }
        }
}

Function SSRSItem ([string]$ItemType,$item,[string]$folder)
 {
 Write-host ""
 #ReportName
 if ($ItemType -ne "Resource")
 {
 $ItemName = [System.IO.Path]::GetFileNameWithoutExtension($item);
 }
 else
 {
 $ItemName = $item.Name
 }
 write-host $ItemName -ForegroundColor Green 

 #Upload File
     try
    {
        #Get Report content in bytes
        Write-Host "Getting file content of : $item"
        $byteArray = Get-Content $item.FullName -encoding byte
        $msg = "Size: {0} KB" -f "{0:N0}" -f ($byteArray.Length/1KB) 
        Write-Host $msg 
        
        Write-Host "Uploading to: $folder"
 
        #Sets property for images(only png)
        $type = $ssrsProxy.GetType().Namespace
        $datatype = ($type + '.Property')              
        $property =New-Object ($datatype);
        if ($ItemType -eq "Resource" -and $item -like "*.png")
        {
        $property.Name = "MimeType"
        $property.Value = image/png
        }
        else
        {
        $property = $null
        }

        #Call Proxy to upload report  
        $warnings =@();    
        $ssrsProxy.CreateCatalogItem($ItemType,$itemName,$folder,$overwrite,$byteArray,$property,[ref]$warnings) | out-null
        if($warnings.Length -le 1) { Write-Host "Upload Success." -ForegroundColor Green }
        else 
        {        
            foreach ($message in $warnings)
                {
                if ($message.Code -ne "rsDataSourceReferenceNotPublished")
                    {
                    write-host "$($message.Severity) $($message.Code) $($message.Message)" -ForegroundColor Yellow
                    }
                }
        }
    }
    catch [System.IO.IOException]
    {
        $msg = "Error while reading rdl file : '{0}', Message: '{1}'" -f $rdlFile, $_.Exception.Message
        Write-Error msg
    }
    catch [System.Web.Services.Protocols.SoapException]
 {
    $caught = $false
            if ($_.Exception.Detail.InnerText -match 'rsItemAlreadyExists400')
            {
                $caught = $true
                Write-Host "Report: $itemName already exists." -ForegroundColor Red
            }
            if ($_.Exception.Detail.InnerText -match 'CoretechSSRS')
            {
                $caught = $true
                Write-Host "Cant't find Reporting Extention File." -ForegroundColor Red
            }
            elseif ($caught -eq $false)
            {
                $msg = "Error uploading report: $reportName. Msg: '{0}'" -f $_.Exception.Detail.InnerText
                Write-Error $msg
            }
  
 }
}

Function SSRSDatasource ([string]$ReportPath,$DataSourceName,$DataSourcePath)
{
$report = $ssrsProxy.GetItemDataSources($ReportPath)
ForEach ($Source in $report)
    {
    $proxyNamespace = $Source.GetType().Namespace
        $constDatasource = New-Object ("$proxyNamespace.DataSource")
        $constDatasource.Name = $DataSourceName
        $constDatasource.Item = New-Object ("$proxyNamespace.DataSourceReference")
        $constDatasource.Item.Reference = $DataSourcePath

   $Source.item = $constDatasource.Item
    $ssrsProxy.SetItemDataSources($ReportPath, $Source)
    Write-Host "Changing datasource `"$($Source.Name)`" to $($Source.Item.Reference)"
    }
}

##Create Folder Structure
#Create Base Folder

Write-Host "Creating Folders Structure:" -ForegroundColor DarkMagenta
SSRSFolder $reportFolder "/"

ForEach($Folder in Get-ChildItem $SourceDirectory -Directory)
    {
    #Add each folder in the sourcefolder to the reporting service 
    SSRSFolder $Folder.Name /$reportFolder
    }

     
#Upload Reports in root folder
ForEach ($rdlfile in Get-ChildItem $SourceDirectory -Filter *.rdl )
{
SSRSItem Report $rdlfile /$reportFolder

#Change Report Datasource
$ReportPath = "/" + $reportFolder + "/" +  $rdlfile.BaseName 
SSRSDatasource $ReportPath $DataSourceName $DataSourcePath
}

##For Each folder
 
ForEach($Folder in Get-ChildItem $SourceDirectory -Directory)
    {
    #Add each folder in the sourcefolder to the reporting service 
    #SSRSFolder $Folder.Name /$Basefolder 
    
    #Add reports in the folder
    ForEach ($rdlfile in Get-ChildItem $Folder -Filter *.rdl )
        {
        SSRSItem Report $rdlfile /$reportFolder/$($folder.Name) 

        #Change Report Datasource
        $ReportPath = "/" + $reportFolder + "/" + $folder.Name + "/" + $rdlfile.BaseName 
        SSRSDatasource $ReportPath $DataSourceName $DataSourcePath
        }
    }

16 comments:

  1. Great Script... Does not appear to grab sub folders as I am getting this error:
    Get-ChildItem: A parameter cannot be fond that matches parameter name 'Directory'

    All Reports in main sourcedirectory are uploading just fine

    ReplyDelete
    Replies
    1. what version of powershell are you running?
      My first guess would be that the "Directory" parameter is not available in lower powershell versions.

      Delete
    2. Yep right on... Was running from Server 2008 R2... I ran from Server 2012 R2 and was good to go... Thanks, Great Script!

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Hi, first of all thanks very much for this script, I'm learning a lot from it. I was wondering if you could point me in the direction of how to make the script include subfolders? I've been playing with the -recurse flag but I can only seem to get the subfodler reports, not the full structure.. Thanks

    ReplyDelete
  4. This is a very helpful script, just keep in mind not everyone uses the default namespace, I would recommend adding a parameter for an instance name. Your proxy connector would need to be /ReportServer$/

    ReplyDelete
  5. In case anyone else is coming here, Microsoft has started a github project called ReportingServicesTools that works for SQL Server 2012 and up, PowerShell 3 and up, and Windows 7 and up. You can also get it from the PowershellGallery

    ReplyDelete
  6. awesome script, saved me hours of ballache uploading 300 reports 1 by 1!!
    Things I noted were....if the script is above the folder with the reports in, in my case the script was in C:\Users\LocalAdmin\Documents\Reports and the reports (all in subfolders) was in C:\Users\LocalAdmin\Documents\Reports\UAT it looked in C:\Users\LocalAdmin\Documents\Reports\\ (missing the UAT folder out) for the reports and couldnt find them, once I moved the script into the UAT folder it all worked fine, plus I removed the datasource stuff as I didnt need it.
    Thank you again.

    ReplyDelete
  7. Hi there, thanks so much for the script it is great and does everything that I need. Except I really need it not to create a folder called "Demo" first as in your example. I do not wish to enter a initial report folder name to be created, the structure of the folders where the script is located is how they should be and the shouldnt be uploaded into another folder.

    I have got this to partly work but it is no longer building the folder structure. I have two folders Reports 1 and Reports 1.1 the last should be in Reports 1 but it is at the same level. Please can you assist on how you would modify your script to stop the initial folder creation? I need my current structure to be created in the Home directory.

    Thanks,
    Glenn

    ReplyDelete
  8. Here's my own take on popular and less popular methods of internet marketing techniques free and paid, and their benefits. I also try to touch base on how to utilize them the best you can, getting the most value out of them. Remember not every method is ideal for every type of site, and a successful advertising campaign will generally contain several methods to varying degrees. ipucum

    ReplyDelete
  9. I was running this as per example but got the following error message:
    New-WebServiceProxy: the HTML document does not contain Web sevice discovery informatio
    .
    .
    .
    You cannot call a method on a null-valued expression
    At C:\UploadReportsv3.ps1:135 char:9

    Do you know what was wrong here?

    ReplyDelete
  10. I ran into the same issue as RicO'Shea: "You cannot call a method on a null-valued expression." Anyone else run into this?

    ReplyDelete
    Replies
    1. Oops, I had the filename wrong, and I had quotes around my server name. Otherwise works great except for a couple of "Exception calling "SetItemDataSources" with "2" argument(s): "The path of the item '' is not valid. The full path must be less than 260 characters long; other restrictions apply." errors. Those might be because of bad datasources.

      Delete
  11. It only seems to go one folder deep. Has anyone found a way to make it recursive?

    ReplyDelete
  12. I ran into the same issue as RicO'Shea : You cannot call a method on a null-valued expression
    At C:\UploadReportsv3.ps1:135 char:9

    Please help us on this issue.

    ReplyDelete