Deploy SSRS Reports from a package

Octopus.Script exported 2021-03-11 by twerthi belongs to ‘SQL Server’ category.

Uploads SSRS reports to an SSRS server from a package.

The following Datasource properties can be overidden: ConnectionString, Username, Password, and CredentialRetrieval (valid values are: Integrated, Prompt, Store, or None). To override the property, create a Variable using the syntax of DatasourceName.PropertyName. For example: MyDatasource.Username

To specify the Username and Password are Windows Credentials, create a variable called DatasourceName.WindowsCredentials and set the value to the string value ‘true’ (minus the quotes).

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

SSRS package step

NugetPackageStepName

Select the step in this project which downloads the SSRS package.

Url of SSRS Server service

ReportServiceUrl

The complete Url to the SSRS server. Example: http://198.239.216.46/ReportServer_LOCALDEV/reportservice2010.asmx?wsdl

Report Execution Url

ReportExecutionUrl

The complete Url to the Report Execution service. Example: http://198.239.216.46/ReportServer_LOCALDEV/ReportExecution2005.asmx?wsdl

Report folder

ReportFolder

Relative Url to the folder in which the reports will be deployed.

Report data source folder

ReportDatasourceFolder

Relative Url where the data sources for the reports are located, starting with ’/‘

Overwrite datasource(s)

OverwriteDataSources = False

Tick if the existing report data source file needs to e replaced.

Backup Location

BackupLocation

Directory path to take a backup of existing reports (.rdl) and data source (.rds) files.

DataSet folder

ReportDataSetFolder

Relative Url where Shared Data Sets are stored

Report Parts Folder

ReportPartsFolder

Relative folder where Report Parts are uploaded to.

Service Domain

ServiceUserDomain

(Optional - leave blank to use Tentacle identity) Name of the domain for the user account to execute as

Service Username

ServiceUserName

(Optional - leave blank to use Tentacle identity) Username of the user account to execute as

Service Password

ServicePassword

(Optional - leave blank to use Tentacle identity) Password of the user account to execute as

Clear the Report Folder

ClearReportFolder = false

Optional - This will delete all items in the Report Folder before adding items.

Use package folder structure

UseArchiveStructure = false

Tick this box to create folders matching the package folder structure and upload items into their respective folders. Ticking this box ignores all other folder specifications except Root folder

Root folder

RootFolder

Used only when ‘Use package folder structure’ is checked. This specifies the root folder on SSRS to start in. Value is relative so it begins with a /

Script body

Steps based on this template will execute the following PowerShell script.

$DeployedPath = $OctopusParameters["Octopus.Action[$NugetPackageStepName].Output.Package.InstallationDirectoryPath"]
$ReleaseNumber = $OctopusParameters["Octopus.Release.Number"]
$UseArchiveStructure = [Convert]::ToBoolean($OctopusParameters["UseArchiveStructure"])

#region Upload-Item
Function Upload-Item 
{
    # parameters
    param ([string] $Item, [string]$ItemType, [string] $ItemFolder)

    Write-Host "Loading data from $Item"
    $ItemData = [System.IO.File]::ReadAllBytes($Item)

    # Create local variables
    $Warnings = $null
    $ItemName = $Item.SubString($Item.LastIndexOf("\") + 1)
    #$ItemName = $ItemName.SubString(0, $ItemName.IndexOf("."))
    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf("."))
   
	# upload item
    if ($IsReportService2005) {
        if($ItemType -eq "Report")
        {
	        [void]$ReportServerProxy.CreateReport($ItemName, $ItemFolder, $true, $ItemData, $null)
        }
        else
        {
            # error
            Write-Error "$ItemType is not supported in ReportService2005"
        }
	}
	elseif ($IsReportService2010) {
		[void]$ReportServerProxy.CreateCatalogItem($ItemType, $ItemName, $ItemFolder, $true, $ItemData, $null, [ref] $Warnings);
	}
	else { Write-Warning 'Report Service Unknown in Upload-Item method. Use ReportService2005 or ReportService2010.' }
}
#endregion

#region Get-ItemDataSourceNames()
Function Get-ItemDataSourceNames
{
    # Parameters
    Param ($ItemFile, $DataSourceName)

    # declare working variables
    $DataSourceNames = @()
    
    # load the xml
    [xml]$Xml = Get-Content $ItemFile

    # retrieve the datasource nodes
    $DataSourceReferenceNodes = $Xml.GetElementsByTagName("DataSource")

    # loop through returned results
    foreach($Node in $DataSourceReferenceNodes)
    {
        # check to see if we're looking for a specific one
        if($DataSourceName -ne $null)
        {
            # check to see if it's the current node
            if($DataSourceName -eq $Node.Name)
            {
                # add
                $DataSourceNames += $Node.DataSourceReference
            }
        }
        else
        {
            # store the name
            $DataSourceNames += $Node.DataSourceReference
        }
    }

    # return the results
    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?
}
#endregion

#region Get-ItemDataSources()
Function Get-ItemDataSources
{
    # Parameters
    Param ($ItemFile)

    # declare working variables
    $DataSourceNames = @()
    
    # load the xml
    [xml]$Xml = Get-Content $ItemFile

    # retrieve the datasource nodes
    $DataSourceReferenceNodes = $Xml.GetElementsByTagName("DataSource")

    # loop through returned results
    foreach($Node in $DataSourceReferenceNodes)
    {
        # store the name
        $DataSourceNames += $Node.Name
    }

    # return the results
    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?
}
#endregion

#region Get-ItemDataSourceReferenceNames
Function Get-ItemDataSourceReferenceNames
{
    # Parameters
    Param ($ItemFile)

    # declare working variables
    $DataSourceNames = @()
    
    # load the xml
    [xml]$Xml = Get-Content $ItemFile

    # retrieve the datasource nodes
    $DataSourceReferenceNodes = $Xml.GetElementsByTagName("DataSourceReference")

    # loop through returned results
    foreach($Node in $DataSourceReferenceNodes)
    {
        # get the data
        $DataSourceNames += $Node.InnerText
    }

    # return the results
    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?
}
#endregion

#region Get-DataSetSharedReferenceName
Function Get-DataSetSharedReferenceName
{
    # parameters
    param($ReportFile, $DataSetName)

    # load the xml
    [xml]$ReportXml = Get-Content $ReportFile

    # Get the DataSet nodes
    $DataSetNode = $ReportXml.GetElementsByTagName("DataSet") | Where-Object {$_.Name -eq $DataSetName}

    # return the name
    $DataSetNode.SharedDataSet.SharedDataSetReference
}
#endregion

#region Item-Exists()
Function Item-Exists($ItemFolderPath, $ItemName)
{
    # declare search condition
    $SearchCondition = New-Object "$ReportServerProxyNamespace.SearchCondition";

    # fill in properties
    $SearchCondition.Condition = Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.ConditionEnum" -EnumName "Equals"
    $SearchCondition.ConditionSpecified = $true
    $SearchCondition.Name = "Name"

	if ($IsReportService2005) {
		$SearchCondition.Value = $ItemName
		# search
	    $items = $ReportServerProxy.FindItems($ItemFolderPath, (Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.BooleanOperatorEnum" -EnumName "And"), $SearchCondition)
	}
	elseif ($IsReportService2010) {
		$SearchCondition.Values = @($ItemName)
		# search
	    $items = $ReportServerProxy.FindItems($ItemFolderPath, (Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.BooleanOperatorEnum" -EnumName "And"), $null, $SearchCondition)
	}
	else { Write-Warning 'Report Service Unknown in Item-Exists method. Use ReportService2005 or ReportService2010.' }    

    # check to see if anything was returned
    if($items.Length -gt 0)
    {
        # loop through returned items
        foreach($item in $items)
        {
            # check the path
            if($item.Path -eq "$ItemFolderPath/$ItemName")
            {
                # return true
                return $true
            }
            else
            {
                # warn
                Write-Warning "Unexpected path for $($item.Name); path is $($item.Path) exepected $ItemFolderPath/$ItemName"
            }
        }

        # items were found, but the path doesn't match
        
        return $false
    }
    else
    {
        return $false
    }
}
#endregion

Function Get-ItemPath
{
    # Define parameters
    param(
    $ItemName,
    $StartFolder,
    $CompareFolderPath)

    # declare search condition
    $SearchCondition = New-Object "$ReportServerProxyNamespace.SearchCondition";

    # fill in properties
    $SearchCondition.Condition = Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.ConditionEnum" -EnumName "Equals"
    $SearchCondition.ConditionSpecified = $true
    $SearchCondition.Name = "Name"

	if ($IsReportService2005) {
		$SearchCondition.Value = $ItemName
		# search
	    $items = $ReportServerProxy.FindItems($StartFolder, (Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.BooleanOperatorEnum" -EnumName "And"), $SearchCondition)
	}
	elseif ($IsReportService2010) {
		$SearchCondition.Values = @($ItemName)
		# search
	    $items = $ReportServerProxy.FindItems($StartFolder, (Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.BooleanOperatorEnum" -EnumName "And"), $null, $SearchCondition)
	}
	else { Write-Warning 'Report Service Unknown in Item-Exists method. Use ReportService2005 or ReportService2010.' }    

    # Check how many items were returned
    if ($items.Length -eq 1)
    {
        return $items[0].Path
    }
    else
    {
        # Loop through returned items
        foreach ($item in $items)
        {
            # compare folders
            if ($CompareFolderPath -eq ($item.Path.SubString(0, $item.Path.LastIndexOf("/"))))
            {
                # Display message we're guessing
                Write-Host "Multiple items were found with name $ItemName, assuming location is same folder as reference, $CompareFolderPath."
                return $item.Path
            }
        }

        # Display warning
        Write-Warning "Multiple items were returned for $ItemName, unable to determine which one to return."
        return [string]::Empty
    }    
}

#region Set-ItemDataSources()
Function Set-ItemDataSources
{
    # parameters
    Param($ItemFile, $ItemFolder)

    # declare local variables
    $ItemName = $ItemFile.SubString($ItemFile.LastIndexOf("\") + 1)
    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf("."))
    $AllDataSourcesFound = $true
    
    # get the datasources
    $DataSources = $ReportServerProxy.GetItemDataSources([string]::Format("{0}/{1}", $ItemFolder, $ItemName))

    #loop through retrieved datasources
    foreach($DataSource in $DataSources)
    {
        # check to see if it's a dataset
        if([System.IO.Path]::GetExtension($ItemFile).ToLower() -eq ".rsd")
        {
            # datasets can have one and only one datasource
            # The method GetItemDataSources does not return the name of the datasource for datasets like it does for reports
            # instead, it alaways returns DataSetDataSource.  This made the call to Get-ItemDataSourceNames necessary,
            # otherwise it would not link correctly
            $DataSourceName = (Get-ItemDataSourceReferenceNames -ItemFile $ItemFile)[0]
        }
        else
        {
            # get the anme from teh source itself
            $DataSourceName = (Get-ItemDataSourceNames -ItemFile $ItemFile -DataSourceName $DataSource.Name)[0]
        }        

        if ([string]::IsNullOrWhiteSpace($DataSourceName))
        {
            Write-Host "Datasource $($DataSource.Name) is not a shared datasource, skipping."
            $AllDataSourcesFound = $false
            continue
        }

        # Check to see if datasourcename contains the folder -- this can happen if the report was created by Report Builder
        if((![string]::IsNullOrEmpty($ReportDataSource)) -and ($DataSourceName.ToLower().Contains($ReportDatasourceFolder.ToLower())))
        {
            # Remove teh path from the item name
            $DataSourceName = $DataSourceName.ToLower().Replace("$($ReportDatasourceFolder.ToLower())/","")
        }

        $DatasourcePath = ""

        if ($UseArchiveStructure -eq $true)
        {
            $DatasourcePath = Get-ItemPath -ItemName $DataSourceName -StartFolder $RootFolder -CompareFolderPath $ItemFolder
            $DatasourcePath = $DatasourcePath.SubString(0, $DatasourcePath.LastIndexOf("/"))
        }
        else
        {
            $DatasourcePath = $ReportDatasourceFolder
        }

        # check to make sure the datasource exists in the location specified
        if((Item-Exists -ItemFolderPath $DatasourcePath -ItemName $DataSourceName) -eq $true)
        {
            # create datasource reference variable
            $DataSourceReference = New-Object "$ReportServerProxyNamespace.DataSourceReference";

            # assign
            $DataSourceReference.Reference = "$DatasourcePath/" + $DataSourceName
            $DataSource.Item = $DataSourceReference            
        }
        else
        {
            # display warning
            Write-Warning "Unable to find datasource $($DataSourceName) in $DatasourcePath"

            # update to false
            $AllDataSourcesFound = $false
        }        
    }

    # check to see if found all datasources
    if($AllDataSourcesFound -eq $true)
    {
        Write-Host "Linking datasources to $ItemFolder/$ItemName"
        
        # save the references
        $ReportServerProxy.SetItemDataSources("$ItemFolder/$ItemName", $DataSources)
    }
}
#endregion

#region Set-ReportDataSets()
Function Set-ReportDataSets
{
    # parameters
    param($ReportFile, $ReportFolderPath)

    # declare local variables
    $ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("\") + 1)
    $ReportName = $ReportName.SubString(0, $ReportName.LastIndexOf("."))
    $AllDataSetsFound = $true
    $DataSetFolder = ""

    # get the datasources
    $DataSets = $ReportServerProxy.GetItemReferences([string]::Format("{0}/{1}", $ReportFolderPath, $ReportName), "DataSet")

    # loop through returned values
    foreach($DataSet in $DataSets)
    {
        # get the name of the shared data set reference
        $SharedDataSetReferenceName = Get-DataSetSharedReferenceName -ReportFile $ReportFile -DataSetName $DataSet.Name

        # Check to see if the SharedDataSetReferenceName contains the folder path -- this can happen if the report was built using Report Builder
        if((![string]::IsNullOrEmpty($ReportDataSetFolder)) -and ($SharedDataSetReferenceName.ToLower().Contains($ReportDataSetFolder.ToLower())))
        {
            # Remove the folder path from the name, it will cause issues when trying to set
            $SharedDataSetReferenceName = $SharedDataSetReferenceName.ToLower().Replace("$($ReportDataSetFolder.ToLower())/", "")
        }
        
        # Check to see if we're using the archive folder structure
        if ($UseArchiveStructure -eq $true)
        {
            # Set dataset folder
            $DataSetFolder = Get-ItemPath -ItemName $SharedDataSetReferenceName -StartFolder $RootFolder -CompareFolderPath $ReportFolderPath
            $DataSetFolder = $DataSetFolder.SubString(0, $DataSetFolder.LastIndexOf("/"))
        }
        else
        {
            $DataSetFolder = $ReportDataSetFolder
        }

        # check to make sure the datasource exists in the location specified
        if((Item-Exists -ItemFolderPath $DataSetFolder -ItemName $SharedDataSetReferenceName) -eq $true)
        {
            # create datasource reference variable
            $DataSetReference = New-Object "$ReportServerProxyNamespace.ItemReference";

            # assign
            $DataSetReference.Reference = "$DataSetFolder/" + $SharedDataSetReferenceName
            $DataSetReference.Name = $DataSet.Name

            # log
            Write-Host "Linking Shared Data Set $($DataSet.Name) to $ReportName"
            
            # update reference
            $ReportServerProxy.SetItemReferences("$ReportFolderPath/$ReportName", @($DataSetReference))
        }
        else
        {
            # get the datasource name to include in warning message -- I know there must be a way to use the property in a string literal, but I wasn't able to figure it out while trying
            # to solve a reported bug so I took the easy way.
            $DataSetName = $DataSet.Name
            
            # display warning
            Write-Warning "Unable to find dataset $DataSetName in $ReportDataSetFolder"

            # update to false
            $AllDataSetsFound = $false
        }            
    }

    # check to see if all datsets were found
    if($AllDataSetsFound -eq $False)
    {
        Write-Warning "Not all datasets found"

        # save the references
        $ReportServerProxy.SetItemReferences("$ReportFolder/$ReportName", @($DataSets))
    }
}

#endregion

#region Get-ObjectNamespace()
Function Get-ObjectNamespace($Object)
{
    # return the value
    ($Object).GetType().ToString().SubString(0, ($Object).GetType().ToString().LastIndexOf("."))
}
#endregion

#region Get-SpecificEnumValue()
Function Get-SpecificEnumValue($EnumNamespace, $EnumName)
{
    # get the enum values
    $EnumValues = [Enum]::GetValues($EnumNamespace)

    # Loop through to find the specific value
    foreach($EnumValue in $EnumValues)
    {
        # check current
        if($EnumValue -eq $EnumName)
        {
            # return it
            return $EnumValue
        }
    }

    # nothing was found
    return $null
}
#endregion

#region Update-ReportParamters()
Function Update-ReportParameters($ReportFile, $ReportFolderPath)
{
    # declare local variables
    $ReportParameters = @();
    
    # necessary so that when attempting to use the report execution service, it doesn't puke on you when it can't find the data source
    $ReportData = (Remove-SharedReferences -ReportFile $ReportFile)

    # get just the report name
    $ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("\") + 1)
    $ReportName = $ReportName.SubString(0, $ReportName.LastIndexOf("."))
    
    # create warnings object
    $ReportExecutionWarnings = $null

    # set the report full path
    $ReportPath = "$ReportFolderPath/$ReportName" 

    # load the report definition
    $ExecutionInfo = $ReportExecutionProxy.LoadReportDefinition($ReportData, [ref] $ReportExecutionWarnings);

    # loop through the report execution parameters
    foreach($Parameter in $ExecutionInfo.Parameters)
    {
        # create new item parameter object
        $ItemParameter = New-Object "$ReportServerProxyNamespace.ItemParameter";

        # fill in the properties except valid values, that one needs special processing
        Copy-ObjectProperties -SourceObject $Parameter -TargetObject $ItemParameter;

        # fill in the valid values
        $ItemParameter.ValidValues = Convert-ValidValues -SourceValidValues $Parameter.ValidValues;

        # exclude if it's query based
        if($Parameter.DefaultValuesQueryBased -ne $true)
        {
            # add to list
            $ReportParameters += $ItemParameter;
        }
    }

    # force the parameters to update
    Write-Host "Updating report parameters for $ReportFolderPath/$ReportName"
	if ($IsReportService2005) {
		$ReportServerProxy.SetReportParameters("$ReportFolderPath/$ReportName", $ReportParameters);
	}
	elseif ($IsReportService2010) {
		$ReportServerProxy.SetItemParameters("$ReportFolderPath/$ReportName", $ReportParameters);
	}
	else { Write-Warning 'Report Service Unknown in Update-ReportParameters method. Use ReportService2005 or ReportService2010.' }
}
#endregion

#region Remove-ShareReferences()
Function Remove-SharedReferences($ReportFile)
{
    ######################################################################################################
    #You'll notice that I've used the keyword of [void] in front of some of these method calls, this is so
    #that the operation isn't captured as output of the function
    ######################################################################################################

    # read xml
    [xml]$ReportXml = Get-Content $ReportFile -Encoding UTF8;
    
    # create new memory stream object
    $MemoryStream = New-Object System.IO.MemoryStream

    try
    {

        # declare array of nodes to remove
        $NodesToRemove = @();

        # get datasource names
        $DataSourceNames = Get-ItemDataSources -ItemFile $ReportFile

        # check to see if report has datasourcenames
        if($DataSourceNames.Count -eq 0)
        {
            # Get reference to reportnode
            $ReportNode = $ReportXml.FirstChild.NextSibling # Kind of a funky way of getting it, but the SelectSingleNode("//Report") wasn't working due to Namespaces in the node

            # create new DataSources node
            $DataSourcesNode = $ReportXml.CreateNode($ReportNode.NodeType, "DataSources", $null)

            # create new datasource node
            $DataSourceNode = $ReportXml.CreateNode($ReportNode.NodeType, "DataSource", $null)

            # create new datasourcereference node
            $DataSourceReferenceNode = $ReportXml.CreateNode($ReportNode.NodeType, "DataSourceReference", $null)

            # create new attribute
            $DataSourceNameAttribute = $ReportXml.CreateAttribute("Name")
            $DataSourceNameAttribute.Value = "DataSource1"
            $dataSourceReferenceNode.InnerText = "DataSource1"

            # add attribute to datasource node
            [void]$DataSourceNode.Attributes.Append($DataSourceNameAttribute)
            [void]$DataSourceNode.AppendChild($DataSourceReferenceNode)

            # add nodes
            [void]$ReportNode.AppendChild($DataSourcesNode)
            [void]$DataSourcesNode.AppendChild($DataSourceNode)

            # add fake datasource name to array
            $DataSourceNames += "DataSource1"
        }

        # get all datasource nodes
        $DatasourceNodes = $ReportXml.GetElementsByTagName("DataSourceReference");

        # loop through returned nodes
        foreach($DataSourceNode in $DatasourceNodes)
        {
            # create a new connection properties node
            $ConnectionProperties = $ReportXml.CreateNode($DataSourceNode.NodeType, "ConnectionProperties", $null);

            # create a new dataprovider node
            $DataProvider = $ReportXml.CreateNode($DataSourceNode.NodeType, "DataProvider", $null);
            $DataProvider.InnerText = "SQL";

            # create new connection string node
            $ConnectString = $ReportXml.CreateNode($DataSourceNode.NodeType, "ConnectString", $null);
            $ConnectString.InnerText = "Data Source=Server Name Here;Initial Catalog=database name here";

            # add new node to parent node
            [void] $DataSourceNode.ParentNode.AppendChild($ConnectionProperties);

            # append childeren
            [void] $ConnectionProperties.AppendChild($DataProvider);
            [void] $ConnectionProperties.AppendChild($ConnectString);

            # Add to remove list 
            $NodesToRemove += $DataSourceNode;
        }

        # get all shareddataset nodes
        $SharedDataSetNodes = $ReportXml.GetElementsByTagName("SharedDataSet")

        #loop through the returned nodes
        foreach($SharedDataSetNode in $SharedDataSetNodes)
        {
            # create holder nodes so it won't error
            $QueryNode = $ReportXml.CreateNode($SharedDataSetNode.NodeType, "Query", $null);
            $DataSourceNameNode = $ReportXml.CreateNode($QueryNode.NodeType, "DataSourceName", $null);
            $CommandTextNode = $ReportXml.CreateNode($QueryNode.NodeType, "CommandText", $null);

            # add valid datasource name, just get the first in the list
            $DataSourceNameNode.InnerText = $DataSourceNames[0]
            
            # add node to parent
            [void] $SharedDataSetNode.ParentNode.Appendchild($QueryNode)
            
            # add datasourcename and commandtext to query node
            [void]$QueryNode.AppendChild($DataSourceNameNode)
            [void]$QueryNode.AppendChild($CommandTextNode)

            # add node to removelist
            $NodesToRemove += $SharedDataSetNode
        }


        # loop through nodes to remove
        foreach($Node in $NodesToRemove)
        {
            # remove from parent
            [void] $Node.ParentNode.RemoveChild($Node);
        }
    
        $ReportXml.InnerXml = $ReportXml.InnerXml.Replace("xmlns=`"`"", "")

        # save altered xml to memory stream
        $ReportXml.Save($MemoryStream);

        # return the altered xml as byte array
        return $MemoryStream.ToArray();
    }
    finally
    {
        # close and dispose
        $MemoryStream.Close();
        $MemoryStream.Dispose();
    }
}
#endregion


#region Copy-ObjectProperties()
Function Copy-ObjectProperties($SourceObject, $TargetObject)
{
    # Get source object property array
    $SourcePropertyCollection = $SourceObject.GetType().GetProperties();

    # get the destination
    $TargetPropertyCollection = $TargetObject.GetType().GetProperties();

    # loop through source property collection
    for($i = 0; $i -lt $SourcePropertyCollection.Length; $i++)
    {
        # get the target property
        $TargetProperty = $TargetPropertyCollection | Where {$_.Name -eq $SourcePropertyCollection[$i].Name}
        
        # check to see if it's null
        if($TargetProperty -ne $null)
        {
            # check to see if it's the valid values property
            if($TargetProperty.Name -ne "ValidValues")
            {
                 # set the value
                $TargetProperty.SetValue($TargetObject, $SourcePropertyCollection[$i].GetValue($SourceObject));
            }
        }
    }
}
#endregion

#region ConvertValidValues()
Function Convert-ValidValues($SourceValidValues)
{
    # declare local values
    $TargetValidValues = @();
    
    # loop through source values
    foreach($SourceValidValue in $SourceValidValues)
    {
        # create target valid value object
        $TargetValidValue = New-Object "$ReportServerProxyNamespace.ValidValue";

        # copy properties
        Copy-ObjectProperties -SourceObject $SourceValidValue -TargetObject $TargetValidValue

        # add to list
        $TargetValidValues += $TargetValidValue
    }

    # return the values
    return ,$TargetValidValues
}
#endregion

#region Backup-ExistingItem()
Function Backup-ExistingItem
{
    # parameters
    Param($ItemFile, $ItemFolder)

    # declare local variables
    $ItemName = $ItemFile.SubString($ItemFile.LastIndexOf("\") + 1)
    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf("."))

    # check to see if the item exists
    if((Item-Exists -ItemFolderPath $ItemFolder -ItemName $ItemName) -eq $true)
    {
        # get file extension
        $FileExtension = [System.IO.Path]::GetExtension($ItemFile)
    
        # check backuplocation
        if($BackupLocation.EndsWith("\") -ne $true)
        {
            # append ending slash
            $BackupLocation = $BackupLocation + "\"
        }
		
		# add the release number to the backup location
		$BackupLocation = $BackupLocation + $ReleaseNumber + "\"

        # ensure the backup location actually exists
        if((Test-Path $BackupLocation) -ne $true)
        {
            # create it
            New-Item -ItemType Directory -Path $BackupLocation
        }

        # download the item
        $Item = $ReportServerProxy.GetItemDefinition("$ItemFolder/$ItemName")

        # form the backup path
        $BackupPath = "{0}{1}{2}" -f $BackupLocation, $ItemName, $FileExtension;

        # write to disk
        [System.IO.File]::WriteAllBytes("$BackupPath", $Item);

        # write to screen
        Write-Host "Backed up $ItemFolder/$ItemName to $BackupPath";
    }
}
#endregion

#region Normalize-SSRSFolder()
function Normalize-SSRSFolder ([string]$Folder) {
    if (-not $Folder.StartsWith('/')) {
        $Folder = '/' + $Folder
    }
    
    return $Folder
}
#endregion

#region New-SSRSFolder()
function New-SSRSFolder ([string] $Name) {
    Write-Verbose "New-SSRSFolder -Name $Name"
 
    $Name = Normalize-SSRSFolder -Folder $Name
 
    if ($ReportServerProxy.GetItemType($Name) -ne 'Folder') {
        $Parts = $Name -split '/'
        $Leaf = $Parts[-1]
        $Parent = $Parts[0..($Parts.Length-2)] -join '/'
 
        if ($Parent) {
            New-SSRSFolder -Name $Parent
        } else {
            $Parent = '/'
        }
        
        $ReportServerProxy.CreateFolder($Leaf, $Parent, $null)
    }
}
#endregion

#region Clear-SSRSFolder()
function Clear-SSRSFolder ([string] $Name) {
    Write-Verbose "Clear-SSRSFolder -Name $Name"
    
    $Name = Normalize-SSRSFolder -Folder $Name
    
    if ($ReportServerProxy.GetItemType($Name) -eq 'Folder' -and $ClearReportFolder) {
        Write-Host ("Clearing the {0} folder" -f $Name)
        $ReportServerProxy.ListChildren($Name, $false) | ForEach-Object {
            Write-Verbose "Deleting item: $($_.Path)"
            $ReportServerProxy.DeleteItem($_.Path)
        }
    }
}
#endregion

#region New-SSRSDataSource()
function New-SSRSDataSource ([string]$RdsPath, [string]$Folder, [bool]$OverwriteDataSources) {
    Write-Verbose "New-SSRSDataSource -RdsPath $RdsPath -Folder $Folder"

    $Folder = Normalize-SSRSFolder -Folder $Folder
    
    [xml]$Rds = Get-Content -Path $RdsPath
    $dsName = $Rds.RptDataSource.Name
    $ConnProps = $Rds.RptDataSource.ConnectionProperties
    
	$type = $ReportServerProxy.GetType().Namespace #Get proxy type
	$DSDdatatype = ($type + '.DataSourceDefinition')
	 
	$Definition = new-object ($DSDdatatype)
	if($Definition -eq $null){
	 Write-Error Failed to create data source definition object
	}
	
	$dsConnectionString = $($OctopusParameters["$($dsName).ConnectionString"])
    $dsUsername = $($OctopusParameters["$($dsName).Username"])
    $dsPassword = $($OctopusParameters["$($dsName).Password"])
    $dsCredentialRetrieval = $($OctopusParameters["$($dsName).CredentialRetrieval"])
    
	# replace the connection string variable that is configured in the octopus project
	if ($dsConnectionString) {
	    $Definition.ConnectString = $dsConnectionString
	} else {
	    $Definition.ConnectString = $ConnProps.ConnectString
	}
	
    $Definition.Extension = $ConnProps.Extension 

	# Check to see if the credential retrieval is overridden
    if ($null -ne $dsCredentialRetrieval)
    {
    	Write-Host "Forcing CredentialRetrieval property to: $dsCredentialRetrieval."
        $Definition.CredentialRetrieval = $dsCredentialRetrieval
    }
    else
    {
    	# Set the Credential Retrieval method
    	if ([Convert]::ToBoolean($ConnProps.IntegratedSecurity)) {
			$Definition.CredentialRetrieval = 'Integrated'
		}
        elseif (![string]::IsNullOrWhitespace($dsUsername) -and ![string]::IsNullOrWhitespace($dsPassword))
        {
        	$Definition.CredentialRetrieval = 'Store'
        }
    }
       
	if ($Definition.CredentialRetrieval -eq 'Store')
    {		
		Write-Host "$($dsName).Username = '$dsUsername'"
		Write-Host "$($dsName).Password = '$dsPassword'"
		
		$Definition.UserName = $dsUsername;
        $Definition.Password = $dsPassword;
	}
    
    # Check to see if this is supposed to be an Windows Authentication stored account
    if ($OctopusParameters["$($dsName).WindowsCredentials"] -eq "true")
    {
	    # Set the definition to Windows Credentials
    	$Definition.WindowsCredentials = $true
    }
    

    $DataSource = New-Object -TypeName PSObject -Property @{
        Name = $Rds.RptDataSource.Name
        Path =  $Folder + '/' + $Rds.RptDataSource.Name
    }
    
    if ($OverwriteDataSources -or $ReportServerProxy.GetItemType($DataSource.Path) -eq 'Unknown') {
        Write-Host "Overwriting datasource $($DataSource.Name)"
        $ReportServerProxy.CreateDataSource($DataSource.Name, $Folder, $OverwriteDataSources, $Definition, $null)
    }
    
    return $DataSource 
}
#endregion

#region Main

try
{
    # declare array for reports
    $ReportFiles = @()
	$ReportDataSourceFiles = @()
    $ReportDataSetFiles = @()
    $ReportPartFiles = @()
	
	$IsReportService2005 = $false
	$IsReportService2010 = $false
	
	if ($ReportServiceUrl.ToLower().Contains('reportservice2005.asmx')) {
		$IsReportService2005 = $true
		Write-Host "2005 Report Service found."
	}
	elseif ($ReportServiceUrl.ToLower().Contains('reportservice2010.asmx')) {
		$IsReportService2010 = $true
		Write-Host "2010 Report Service found."
	}
	
	Write-Host "Deploy Path: $DeployedPath"
	
    # get all report files for deployment
    Write-Host "Getting all .rdl files"
    Get-ChildItem $DeployedPath -Recurse -Filter "*.rdl" | ForEach-Object { If(($ReportFiles -contains $_.FullName) -eq $false) {$ReportFiles += $_.FullName}}
    Write-Host "# of rdl files found: $($ReportFiles.Count)"

    # get all report datasource files for deployment
    Write-Host "Getting all .rds files"
    Get-ChildItem $DeployedPath -Recurse -Filter "*.rds" | ForEach-Object { If(($ReportDataSourceFiles -contains $_.FullName) -eq $false) {$ReportDataSourceFiles += $_.FullName}}
    Write-Host "# of rds files found: $($ReportDataSourceFiles.Count)"

    # get all report datset files for deployment
    Write-Host "Getting all .rsd files"
    Get-ChildItem $DeployedPath -Recurse -Filter "*.rsd" | ForEach-Object { If(($ReportDataSetFiles -contains $_.FullName) -eq $false) {$ReportDataSetFiles += $_.FullName}}
    Write-Host "# of rsd files found: $($ReportDataSetFiles.Count)"

    # get all report part files for deployment
    Write-Host "Getting all .rsc files"
    Get-ChildItem $DeployedPath -Recurse -Filter "*.rsc" | ForEach-Object { If(($ReportPartFiles -contains $_.FullName) -eq $false) {$ReportPartFiles += $_.FullName}}
    Write-Host "# of rsc files found: $($ReportPartFiles.Count)"

    # set the report proxies
    Write-Host "Creating SSRS Web Service proxies"

    # check to see if credentials were supplied for the services
    if(([string]::IsNullOrEmpty($ServiceUserDomain) -ne $true) -and ([string]::IsNullOrEmpty($ServiceUserName) -ne $true) -and ([string]::IsNullOrEmpty($ServicePassword) -ne $true))
    {
        # secure the password
        $secpasswd = ConvertTo-SecureString "$ServicePassword" -AsPlainText -Force

        # create credential object
        $ServiceCredential = New-Object System.Management.Automation.PSCredential ("$ServiceUserDomain\$ServiceUserName", $secpasswd)

        # create proxies
        $ReportServerProxy = New-WebServiceProxy -Uri $ReportServiceUrl -Credential $ServiceCredential
        $ReportExecutionProxy = New-WebServiceProxy -Uri $ReportExecutionUrl -Credential $ServiceCredential
    }
    else
    {
        # create proxies using current identity
        $ReportServerProxy = New-WebServiceProxy -Uri $ReportServiceUrl -UseDefaultCredential 
        $ReportExecutionProxy = New-WebServiceProxy -Uri $ReportExecutionUrl -UseDefaultCredential 
    }



	#Create folder information for DataSource and Report
    if ($UseArchiveStructure -eq $false)
    {
	    New-SSRSFolder -Name $ReportFolder
	    New-SSRSFolder -Name $ReportDatasourceFolder
        New-SSRSFolder -Name $ReportDataSetFolder
        New-SSRSFolder -Name $ReportPartsFolder
    }
    else
    {
        New-SSRSFolder -Name $RootFolder
    }
    
    #Clear destination folder if specified
    if([System.Convert]::ToBoolean("$ClearReportFolder")) {
        Clear-SSRSFolder -Name $ReportFolder
    }
	 
	#Create DataSource
    foreach($RDSFile in $ReportDataSourceFiles) {
        Write-Host "New-SSRSDataSource $RdsFile"

        $DatasourceFolder = ""

        if ($UseArchiveStructure -eq $true)
        {
            # Adjust report folder to archive path
            $DatasourceFolder = $(if ($RootFolder -eq "/") { [string]::Empty} else { $RootFolder } ) + $RDSFile.Replace($DeployedPath, '').Replace('\', '/').Replace((Split-Path $RDSFile -Leaf), '')

            # Remove final slash
            $DatasourceFolder = $DatasourceFolder.Substring(0, $DatasourceFolder.LastIndexOf('/'))    
            
            # Check if folder exists
            New-SSRSFolder -Name $DatasourceFolder                    
        }
        else
        {
            $DatasourceFolder = $ReportDatasourceFolder
        }
        
		$DataSource = New-SSRSDataSource -RdsPath $RdsFile -Folder $DatasourceFolder -Overwrite ([System.Convert]::ToBoolean("$OverwriteDataSources"))
	}

    # get the service proxy namespaces - this is necessary because of a bug documented here http://stackoverflow.com/questions/7921040/error-calling-reportingservice2005-finditems-specifically-concerning-the-bool and http://www.vistax64.com/powershell/273120-bug-when-using-namespace-parameter-new-webserviceproxy.html
    $ReportServerProxyNamespace = Get-ObjectNamespace -Object $ReportServerProxy
    $ReportExecutionProxyNamespace = Get-ObjectNamespace -Object $ReportExecutionProxy

    # Create dat sets
    foreach($DataSet in $ReportDataSetFiles)
    {
        $DataSetPath = ""

        # Check to see if it's set to follow archive structure
        if ($UseArchiveStructure -eq $true)
        {
            # Adjust report folder to archive path
            $DataSetPath = $(if ($RootFolder -eq "/") { [string]::Empty} else { $RootFolder } ) + $DataSet.Replace($DeployedPath, '').Replace('\', '/').Replace((Split-Path $DataSet -Leaf), '')

            # Remove final slash
            $DataSetPath = $DataSetPath.Substring(0, $DataSetPath.LastIndexOf('/'))

            # Check if folder exists
            New-SSRSFolder -Name $DataSetPath
        }
        else
        {
            $DataSetPath = $ReportDataSetFolder
        }

        # check to see if we need to back up
        if($BackupLocation -ne $null -and $BackupLocation -ne "")
        {
            # backup the item
            Backup-ExistingItem -ItemFile $DataSet -ItemFolder $DataSetPath
        }

        # upload the dataset
        Upload-Item -Item $DataSet -ItemType "DataSet" -ItemFolder $DataSetPath

        # update the dataset datasource
        Set-ItemDataSources -ItemFile $DataSet -ItemFolder $DataSetPath
    }

    # get the proxy auto generated name spaces

    # loop through array
    foreach($ReportFile in $ReportFiles)
    {
        $ReportPath = ""

        # Check to see if it's set to follow archive structure
        if ($UseArchiveStructure -eq $true)
        {
            # Adjust report folder to archive path
            $ReportPath = $(if ($RootFolder -eq "/") { [string]::Empty} else { $RootFolder } ) + $ReportFile.Replace($DeployedPath, '').Replace('\', '/').Replace((Split-Path $ReportFile -Leaf), '')

            # Remove final slash
            $ReportPath = $ReportPath.Substring(0, $ReportPath.LastIndexOf('/'))

            # Check if folder exists
            New-SSRSFolder -Name $ReportPath
        }
        else
        {
            $ReportPath = $ReportFolder
        }

        # check to see if we need to back up
        if($BackupLocation -ne $null -and $BackupLocation -ne "")
        {
            # backup the item
            Backup-ExistingItem -ItemFile $ReportFile -ItemFolder $ReportPath
        }
        
        
        # upload report
        Upload-Item -Item $ReportFile -ItemType "Report" -ItemFolder $ReportPath

        # extract datasources
        #Write-Host "Extracting datasource names for $ReportFile"
        #$ReportDataSourceNames = Get-ReportDataSourceNames $ReportFile

        # set the datasources
        Set-ItemDataSources -ItemFile $ReportFile -ItemFolder $ReportPath

        # set the datasets
        Set-ReportDataSets -ReportFile $ReportFile -ReportFolderPath $ReportPath

        # update the report parameters
        Update-ReportParameters -ReportFile $ReportFile -ReportFolderPath $ReportPath
    }
    
    # loop through rsc files
    foreach($ReportPartFile in $ReportPartFiles)
    {
        # check to see if we need to back up
        if($BackupLocation -ne $null -and $BackupLocation -ne "")
        {
            # backup the item
            Backup-ExistingItem -ItemFile $ReportPartFile -ItemFolder $ReportPartsFolder
        }

		# upload item
        Upload-Item -Item $ReportPartFile -ItemType "Component" -ItemFolder $ReportPartsFolder
    }
    
}
finally
{
    # check to see if the proxies are null
    if($ReportServerProxy -ne $null)
    {
        # dispose
        $ReportServerProxy.Dispose();
    }

    if($ReportExecutionProxy -ne $null)
    {
        # dispose
        $ReportExecutionProxy.Dispose();
    }
}

#endregion

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "4e3a1163-e157-4675-a60c-4dc569d14348",
  "Name": "Deploy SSRS Reports from a package",
  "Description": "Uploads SSRS reports to an SSRS server from a package.\n\nThe following Datasource properties can be overidden: ConnectionString, Username, Password, and CredentialRetrieval (valid values are: Integrated, Prompt, Store, or None).  To override the property, create a Variable using the syntax of DatasourceName.PropertyName.  For example: MyDatasource.Username \n\nTo specify the Username and Password are Windows Credentials, create a variable called DatasourceName.WindowsCredentials and set the value to the string value 'true' (minus the quotes).",
  "Version": 58,
  "ExportedAt": "2021-03-11T19:19:00.102Z",
  "ActionType": "Octopus.Script",
  "Author": "twerthi",
  "Packages": [],
  "Parameters": [
    {
      "Id": "92daa94e-73f3-466e-a8db-35149646df2b",
      "Name": "NugetPackageStepName",
      "Label": "SSRS package step",
      "HelpText": "Select the step in this project which downloads the SSRS package.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "StepName"
      }
    },
    {
      "Id": "0bb2f003-fda9-4571-86aa-3bad13775874",
      "Name": "ReportServiceUrl",
      "Label": "Url of SSRS Server service",
      "HelpText": "The complete Url to the SSRS server.\nExample: http://198.239.216.46/ReportServer_LOCALDEV/reportservice2010.asmx?wsdl",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "feca8cfe-fe9e-4e8f-ac7e-b370b5ef3ef6",
      "Name": "ReportExecutionUrl",
      "Label": "Report Execution Url",
      "HelpText": "The complete Url to the Report Execution service.\nExample: http://198.239.216.46/ReportServer_LOCALDEV/ReportExecution2005.asmx?wsdl",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "1924b3a6-d5bf-4713-8bc7-6b77422a7944",
      "Name": "ReportFolder",
      "Label": "Report folder",
      "HelpText": "Relative Url to the folder in which the reports will be deployed.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "28cc63a0-5a3e-4d22-8406-574bdb332fe1",
      "Name": "ReportDatasourceFolder",
      "Label": "Report data source folder",
      "HelpText": "Relative Url where the data sources for the reports are located, starting with '/'",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "b1cdc16e-c2db-41eb-9ae8-8df02b05f03d",
      "Name": "OverwriteDataSources",
      "Label": "Overwrite datasource(s)",
      "HelpText": "Tick if the existing report data source file needs to e replaced.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "b0e69f02-1f99-431a-852e-b0084ed23fd4",
      "Name": "BackupLocation",
      "Label": "Backup Location",
      "HelpText": "Directory path to take a backup of existing reports (.rdl) and data source (.rds) files.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "a736f0b8-aa27-45ae-b6a0-43a76e44d85a",
      "Name": "ReportDataSetFolder",
      "Label": "DataSet folder",
      "HelpText": "Relative Url where Shared Data Sets are stored",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "c88b150a-9f70-4700-9981-ed1514dc5a2b",
      "Name": "ReportPartsFolder",
      "Label": "Report Parts Folder",
      "HelpText": "Relative folder where Report Parts are uploaded to.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "47575f66-9b1b-4cbb-a062-30d811e6159d",
      "Name": "ServiceUserDomain",
      "Label": "Service Domain",
      "HelpText": "(Optional - leave blank to use Tentacle identity) Name of the domain for the user account to execute as",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "98b43d45-5a5a-456a-aac7-1f4cc6fe8e55",
      "Name": "ServiceUserName",
      "Label": "Service Username",
      "HelpText": "(Optional - leave blank to use Tentacle identity) Username of the user account to execute as",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "fd48bde1-3874-45ea-ab99-b40c13b91d5a",
      "Name": "ServicePassword",
      "Label": "Service Password",
      "HelpText": "(Optional - leave blank to use Tentacle identity) Password of the user account to execute as",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "1310ffde-1f98-4ebf-bf6a-605bb83ea54e",
      "Name": "ClearReportFolder",
      "Label": "Clear the Report Folder",
      "HelpText": "Optional - This will delete all items in the Report Folder before adding items.",
      "DefaultValue": "false",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "9d64d192-b645-4aac-ba52-3c86aebe8d72",
      "Name": "UseArchiveStructure",
      "Label": "Use package folder structure",
      "HelpText": "Tick this box to create folders matching the package folder structure and upload items into their respective folders.  Ticking this box ignores all other folder specifications except Root folder",
      "DefaultValue": "false",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "ad0280e7-25a6-48cc-bc90-4e6ee3c8951b",
      "Name": "RootFolder",
      "Label": "Root folder",
      "HelpText": "Used only when 'Use package folder structure' is checked.  This specifies the root folder on SSRS to start in.  Value is relative so it begins with a /",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptBody": "$DeployedPath = $OctopusParameters[\"Octopus.Action[$NugetPackageStepName].Output.Package.InstallationDirectoryPath\"]\n$ReleaseNumber = $OctopusParameters[\"Octopus.Release.Number\"]\n$UseArchiveStructure = [Convert]::ToBoolean($OctopusParameters[\"UseArchiveStructure\"])\n\n#region Upload-Item\nFunction Upload-Item \n{\n    # parameters\n    param ([string] $Item, [string]$ItemType, [string] $ItemFolder)\n\n    Write-Host \"Loading data from $Item\"\n    $ItemData = [System.IO.File]::ReadAllBytes($Item)\n\n    # Create local variables\n    $Warnings = $null\n    $ItemName = $Item.SubString($Item.LastIndexOf(\"\\\") + 1)\n    #$ItemName = $ItemName.SubString(0, $ItemName.IndexOf(\".\"))\n    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf(\".\"))\n   \n\t# upload item\n    if ($IsReportService2005) {\n        if($ItemType -eq \"Report\")\n        {\n\t        [void]$ReportServerProxy.CreateReport($ItemName, $ItemFolder, $true, $ItemData, $null)\n        }\n        else\n        {\n            # error\n            Write-Error \"$ItemType is not supported in ReportService2005\"\n        }\n\t}\n\telseif ($IsReportService2010) {\n\t\t[void]$ReportServerProxy.CreateCatalogItem($ItemType, $ItemName, $ItemFolder, $true, $ItemData, $null, [ref] $Warnings);\n\t}\n\telse { Write-Warning 'Report Service Unknown in Upload-Item method. Use ReportService2005 or ReportService2010.' }\n}\n#endregion\n\n#region Get-ItemDataSourceNames()\nFunction Get-ItemDataSourceNames\n{\n    # Parameters\n    Param ($ItemFile, $DataSourceName)\n\n    # declare working variables\n    $DataSourceNames = @()\n    \n    # load the xml\n    [xml]$Xml = Get-Content $ItemFile\n\n    # retrieve the datasource nodes\n    $DataSourceReferenceNodes = $Xml.GetElementsByTagName(\"DataSource\")\n\n    # loop through returned results\n    foreach($Node in $DataSourceReferenceNodes)\n    {\n        # check to see if we're looking for a specific one\n        if($DataSourceName -ne $null)\n        {\n            # check to see if it's the current node\n            if($DataSourceName -eq $Node.Name)\n            {\n                # add\n                $DataSourceNames += $Node.DataSourceReference\n            }\n        }\n        else\n        {\n            # store the name\n            $DataSourceNames += $Node.DataSourceReference\n        }\n    }\n\n    # return the results\n    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?\n}\n#endregion\n\n#region Get-ItemDataSources()\nFunction Get-ItemDataSources\n{\n    # Parameters\n    Param ($ItemFile)\n\n    # declare working variables\n    $DataSourceNames = @()\n    \n    # load the xml\n    [xml]$Xml = Get-Content $ItemFile\n\n    # retrieve the datasource nodes\n    $DataSourceReferenceNodes = $Xml.GetElementsByTagName(\"DataSource\")\n\n    # loop through returned results\n    foreach($Node in $DataSourceReferenceNodes)\n    {\n        # store the name\n        $DataSourceNames += $Node.Name\n    }\n\n    # return the results\n    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?\n}\n#endregion\n\n#region Get-ItemDataSourceReferenceNames\nFunction Get-ItemDataSourceReferenceNames\n{\n    # Parameters\n    Param ($ItemFile)\n\n    # declare working variables\n    $DataSourceNames = @()\n    \n    # load the xml\n    [xml]$Xml = Get-Content $ItemFile\n\n    # retrieve the datasource nodes\n    $DataSourceReferenceNodes = $Xml.GetElementsByTagName(\"DataSourceReference\")\n\n    # loop through returned results\n    foreach($Node in $DataSourceReferenceNodes)\n    {\n        # get the data\n        $DataSourceNames += $Node.InnerText\n    }\n\n    # return the results\n    return ,$DataSourceNames # Apparently using the , in front of the variable is how you return explicit arrays in PowerShell ... could you be more obsure?\n}\n#endregion\n\n#region Get-DataSetSharedReferenceName\nFunction Get-DataSetSharedReferenceName\n{\n    # parameters\n    param($ReportFile, $DataSetName)\n\n    # load the xml\n    [xml]$ReportXml = Get-Content $ReportFile\n\n    # Get the DataSet nodes\n    $DataSetNode = $ReportXml.GetElementsByTagName(\"DataSet\") | Where-Object {$_.Name -eq $DataSetName}\n\n    # return the name\n    $DataSetNode.SharedDataSet.SharedDataSetReference\n}\n#endregion\n\n#region Item-Exists()\nFunction Item-Exists($ItemFolderPath, $ItemName)\n{\n    # declare search condition\n    $SearchCondition = New-Object \"$ReportServerProxyNamespace.SearchCondition\";\n\n    # fill in properties\n    $SearchCondition.Condition = Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.ConditionEnum\" -EnumName \"Equals\"\n    $SearchCondition.ConditionSpecified = $true\n    $SearchCondition.Name = \"Name\"\n\n\tif ($IsReportService2005) {\n\t\t$SearchCondition.Value = $ItemName\n\t\t# search\n\t    $items = $ReportServerProxy.FindItems($ItemFolderPath, (Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.BooleanOperatorEnum\" -EnumName \"And\"), $SearchCondition)\n\t}\n\telseif ($IsReportService2010) {\n\t\t$SearchCondition.Values = @($ItemName)\n\t\t# search\n\t    $items = $ReportServerProxy.FindItems($ItemFolderPath, (Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.BooleanOperatorEnum\" -EnumName \"And\"), $null, $SearchCondition)\n\t}\n\telse { Write-Warning 'Report Service Unknown in Item-Exists method. Use ReportService2005 or ReportService2010.' }    \n\n    # check to see if anything was returned\n    if($items.Length -gt 0)\n    {\n        # loop through returned items\n        foreach($item in $items)\n        {\n            # check the path\n            if($item.Path -eq \"$ItemFolderPath/$ItemName\")\n            {\n                # return true\n                return $true\n            }\n            else\n            {\n                # warn\n                Write-Warning \"Unexpected path for $($item.Name); path is $($item.Path) exepected $ItemFolderPath/$ItemName\"\n            }\n        }\n\n        # items were found, but the path doesn't match\n        \n        return $false\n    }\n    else\n    {\n        return $false\n    }\n}\n#endregion\n\nFunction Get-ItemPath\n{\n    # Define parameters\n    param(\n    $ItemName,\n    $StartFolder,\n    $CompareFolderPath)\n\n    # declare search condition\n    $SearchCondition = New-Object \"$ReportServerProxyNamespace.SearchCondition\";\n\n    # fill in properties\n    $SearchCondition.Condition = Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.ConditionEnum\" -EnumName \"Equals\"\n    $SearchCondition.ConditionSpecified = $true\n    $SearchCondition.Name = \"Name\"\n\n\tif ($IsReportService2005) {\n\t\t$SearchCondition.Value = $ItemName\n\t\t# search\n\t    $items = $ReportServerProxy.FindItems($StartFolder, (Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.BooleanOperatorEnum\" -EnumName \"And\"), $SearchCondition)\n\t}\n\telseif ($IsReportService2010) {\n\t\t$SearchCondition.Values = @($ItemName)\n\t\t# search\n\t    $items = $ReportServerProxy.FindItems($StartFolder, (Get-SpecificEnumValue -EnumNamespace \"$ReportServerProxyNamespace.BooleanOperatorEnum\" -EnumName \"And\"), $null, $SearchCondition)\n\t}\n\telse { Write-Warning 'Report Service Unknown in Item-Exists method. Use ReportService2005 or ReportService2010.' }    \n\n    # Check how many items were returned\n    if ($items.Length -eq 1)\n    {\n        return $items[0].Path\n    }\n    else\n    {\n        # Loop through returned items\n        foreach ($item in $items)\n        {\n            # compare folders\n            if ($CompareFolderPath -eq ($item.Path.SubString(0, $item.Path.LastIndexOf(\"/\"))))\n            {\n                # Display message we're guessing\n                Write-Host \"Multiple items were found with name $ItemName, assuming location is same folder as reference, $CompareFolderPath.\"\n                return $item.Path\n            }\n        }\n\n        # Display warning\n        Write-Warning \"Multiple items were returned for $ItemName, unable to determine which one to return.\"\n        return [string]::Empty\n    }    \n}\n\n#region Set-ItemDataSources()\nFunction Set-ItemDataSources\n{\n    # parameters\n    Param($ItemFile, $ItemFolder)\n\n    # declare local variables\n    $ItemName = $ItemFile.SubString($ItemFile.LastIndexOf(\"\\\") + 1)\n    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf(\".\"))\n    $AllDataSourcesFound = $true\n    \n    # get the datasources\n    $DataSources = $ReportServerProxy.GetItemDataSources([string]::Format(\"{0}/{1}\", $ItemFolder, $ItemName))\n\n    #loop through retrieved datasources\n    foreach($DataSource in $DataSources)\n    {\n        # check to see if it's a dataset\n        if([System.IO.Path]::GetExtension($ItemFile).ToLower() -eq \".rsd\")\n        {\n            # datasets can have one and only one datasource\n            # The method GetItemDataSources does not return the name of the datasource for datasets like it does for reports\n            # instead, it alaways returns DataSetDataSource.  This made the call to Get-ItemDataSourceNames necessary,\n            # otherwise it would not link correctly\n            $DataSourceName = (Get-ItemDataSourceReferenceNames -ItemFile $ItemFile)[0]\n        }\n        else\n        {\n            # get the anme from teh source itself\n            $DataSourceName = (Get-ItemDataSourceNames -ItemFile $ItemFile -DataSourceName $DataSource.Name)[0]\n        }        \n\n        if ([string]::IsNullOrWhiteSpace($DataSourceName))\n        {\n            Write-Host \"Datasource $($DataSource.Name) is not a shared datasource, skipping.\"\n            $AllDataSourcesFound = $false\n            continue\n        }\n\n        # Check to see if datasourcename contains the folder -- this can happen if the report was created by Report Builder\n        if((![string]::IsNullOrEmpty($ReportDataSource)) -and ($DataSourceName.ToLower().Contains($ReportDatasourceFolder.ToLower())))\n        {\n            # Remove teh path from the item name\n            $DataSourceName = $DataSourceName.ToLower().Replace(\"$($ReportDatasourceFolder.ToLower())/\",\"\")\n        }\n\n        $DatasourcePath = \"\"\n\n        if ($UseArchiveStructure -eq $true)\n        {\n            $DatasourcePath = Get-ItemPath -ItemName $DataSourceName -StartFolder $RootFolder -CompareFolderPath $ItemFolder\n            $DatasourcePath = $DatasourcePath.SubString(0, $DatasourcePath.LastIndexOf(\"/\"))\n        }\n        else\n        {\n            $DatasourcePath = $ReportDatasourceFolder\n        }\n\n        # check to make sure the datasource exists in the location specified\n        if((Item-Exists -ItemFolderPath $DatasourcePath -ItemName $DataSourceName) -eq $true)\n        {\n            # create datasource reference variable\n            $DataSourceReference = New-Object \"$ReportServerProxyNamespace.DataSourceReference\";\n\n            # assign\n            $DataSourceReference.Reference = \"$DatasourcePath/\" + $DataSourceName\n            $DataSource.Item = $DataSourceReference            \n        }\n        else\n        {\n            # display warning\n            Write-Warning \"Unable to find datasource $($DataSourceName) in $DatasourcePath\"\n\n            # update to false\n            $AllDataSourcesFound = $false\n        }        \n    }\n\n    # check to see if found all datasources\n    if($AllDataSourcesFound -eq $true)\n    {\n        Write-Host \"Linking datasources to $ItemFolder/$ItemName\"\n        \n        # save the references\n        $ReportServerProxy.SetItemDataSources(\"$ItemFolder/$ItemName\", $DataSources)\n    }\n}\n#endregion\n\n#region Set-ReportDataSets()\nFunction Set-ReportDataSets\n{\n    # parameters\n    param($ReportFile, $ReportFolderPath)\n\n    # declare local variables\n    $ReportName = $ReportFile.SubString($ReportFile.LastIndexOf(\"\\\") + 1)\n    $ReportName = $ReportName.SubString(0, $ReportName.LastIndexOf(\".\"))\n    $AllDataSetsFound = $true\n    $DataSetFolder = \"\"\n\n    # get the datasources\n    $DataSets = $ReportServerProxy.GetItemReferences([string]::Format(\"{0}/{1}\", $ReportFolderPath, $ReportName), \"DataSet\")\n\n    # loop through returned values\n    foreach($DataSet in $DataSets)\n    {\n        # get the name of the shared data set reference\n        $SharedDataSetReferenceName = Get-DataSetSharedReferenceName -ReportFile $ReportFile -DataSetName $DataSet.Name\n\n        # Check to see if the SharedDataSetReferenceName contains the folder path -- this can happen if the report was built using Report Builder\n        if((![string]::IsNullOrEmpty($ReportDataSetFolder)) -and ($SharedDataSetReferenceName.ToLower().Contains($ReportDataSetFolder.ToLower())))\n        {\n            # Remove the folder path from the name, it will cause issues when trying to set\n            $SharedDataSetReferenceName = $SharedDataSetReferenceName.ToLower().Replace(\"$($ReportDataSetFolder.ToLower())/\", \"\")\n        }\n        \n        # Check to see if we're using the archive folder structure\n        if ($UseArchiveStructure -eq $true)\n        {\n            # Set dataset folder\n            $DataSetFolder = Get-ItemPath -ItemName $SharedDataSetReferenceName -StartFolder $RootFolder -CompareFolderPath $ReportFolderPath\n            $DataSetFolder = $DataSetFolder.SubString(0, $DataSetFolder.LastIndexOf(\"/\"))\n        }\n        else\n        {\n            $DataSetFolder = $ReportDataSetFolder\n        }\n\n        # check to make sure the datasource exists in the location specified\n        if((Item-Exists -ItemFolderPath $DataSetFolder -ItemName $SharedDataSetReferenceName) -eq $true)\n        {\n            # create datasource reference variable\n            $DataSetReference = New-Object \"$ReportServerProxyNamespace.ItemReference\";\n\n            # assign\n            $DataSetReference.Reference = \"$DataSetFolder/\" + $SharedDataSetReferenceName\n            $DataSetReference.Name = $DataSet.Name\n\n            # log\n            Write-Host \"Linking Shared Data Set $($DataSet.Name) to $ReportName\"\n            \n            # update reference\n            $ReportServerProxy.SetItemReferences(\"$ReportFolderPath/$ReportName\", @($DataSetReference))\n        }\n        else\n        {\n            # get the datasource name to include in warning message -- I know there must be a way to use the property in a string literal, but I wasn't able to figure it out while trying\n            # to solve a reported bug so I took the easy way.\n            $DataSetName = $DataSet.Name\n            \n            # display warning\n            Write-Warning \"Unable to find dataset $DataSetName in $ReportDataSetFolder\"\n\n            # update to false\n            $AllDataSetsFound = $false\n        }            \n    }\n\n    # check to see if all datsets were found\n    if($AllDataSetsFound -eq $False)\n    {\n        Write-Warning \"Not all datasets found\"\n\n        # save the references\n        $ReportServerProxy.SetItemReferences(\"$ReportFolder/$ReportName\", @($DataSets))\n    }\n}\n\n#endregion\n\n#region Get-ObjectNamespace()\nFunction Get-ObjectNamespace($Object)\n{\n    # return the value\n    ($Object).GetType().ToString().SubString(0, ($Object).GetType().ToString().LastIndexOf(\".\"))\n}\n#endregion\n\n#region Get-SpecificEnumValue()\nFunction Get-SpecificEnumValue($EnumNamespace, $EnumName)\n{\n    # get the enum values\n    $EnumValues = [Enum]::GetValues($EnumNamespace)\n\n    # Loop through to find the specific value\n    foreach($EnumValue in $EnumValues)\n    {\n        # check current\n        if($EnumValue -eq $EnumName)\n        {\n            # return it\n            return $EnumValue\n        }\n    }\n\n    # nothing was found\n    return $null\n}\n#endregion\n\n#region Update-ReportParamters()\nFunction Update-ReportParameters($ReportFile, $ReportFolderPath)\n{\n    # declare local variables\n    $ReportParameters = @();\n    \n    # necessary so that when attempting to use the report execution service, it doesn't puke on you when it can't find the data source\n    $ReportData = (Remove-SharedReferences -ReportFile $ReportFile)\n\n    # get just the report name\n    $ReportName = $ReportFile.SubString($ReportFile.LastIndexOf(\"\\\") + 1)\n    $ReportName = $ReportName.SubString(0, $ReportName.LastIndexOf(\".\"))\n    \n    # create warnings object\n    $ReportExecutionWarnings = $null\n\n    # set the report full path\n    $ReportPath = \"$ReportFolderPath/$ReportName\" \n\n    # load the report definition\n    $ExecutionInfo = $ReportExecutionProxy.LoadReportDefinition($ReportData, [ref] $ReportExecutionWarnings);\n\n    # loop through the report execution parameters\n    foreach($Parameter in $ExecutionInfo.Parameters)\n    {\n        # create new item parameter object\n        $ItemParameter = New-Object \"$ReportServerProxyNamespace.ItemParameter\";\n\n        # fill in the properties except valid values, that one needs special processing\n        Copy-ObjectProperties -SourceObject $Parameter -TargetObject $ItemParameter;\n\n        # fill in the valid values\n        $ItemParameter.ValidValues = Convert-ValidValues -SourceValidValues $Parameter.ValidValues;\n\n        # exclude if it's query based\n        if($Parameter.DefaultValuesQueryBased -ne $true)\n        {\n            # add to list\n            $ReportParameters += $ItemParameter;\n        }\n    }\n\n    # force the parameters to update\n    Write-Host \"Updating report parameters for $ReportFolderPath/$ReportName\"\n\tif ($IsReportService2005) {\n\t\t$ReportServerProxy.SetReportParameters(\"$ReportFolderPath/$ReportName\", $ReportParameters);\n\t}\n\telseif ($IsReportService2010) {\n\t\t$ReportServerProxy.SetItemParameters(\"$ReportFolderPath/$ReportName\", $ReportParameters);\n\t}\n\telse { Write-Warning 'Report Service Unknown in Update-ReportParameters method. Use ReportService2005 or ReportService2010.' }\n}\n#endregion\n\n#region Remove-ShareReferences()\nFunction Remove-SharedReferences($ReportFile)\n{\n    ######################################################################################################\n    #You'll notice that I've used the keyword of [void] in front of some of these method calls, this is so\n    #that the operation isn't captured as output of the function\n    ######################################################################################################\n\n    # read xml\n    [xml]$ReportXml = Get-Content $ReportFile -Encoding UTF8;\n    \n    # create new memory stream object\n    $MemoryStream = New-Object System.IO.MemoryStream\n\n    try\n    {\n\n        # declare array of nodes to remove\n        $NodesToRemove = @();\n\n        # get datasource names\n        $DataSourceNames = Get-ItemDataSources -ItemFile $ReportFile\n\n        # check to see if report has datasourcenames\n        if($DataSourceNames.Count -eq 0)\n        {\n            # Get reference to reportnode\n            $ReportNode = $ReportXml.FirstChild.NextSibling # Kind of a funky way of getting it, but the SelectSingleNode(\"//Report\") wasn't working due to Namespaces in the node\n\n            # create new DataSources node\n            $DataSourcesNode = $ReportXml.CreateNode($ReportNode.NodeType, \"DataSources\", $null)\n\n            # create new datasource node\n            $DataSourceNode = $ReportXml.CreateNode($ReportNode.NodeType, \"DataSource\", $null)\n\n            # create new datasourcereference node\n            $DataSourceReferenceNode = $ReportXml.CreateNode($ReportNode.NodeType, \"DataSourceReference\", $null)\n\n            # create new attribute\n            $DataSourceNameAttribute = $ReportXml.CreateAttribute(\"Name\")\n            $DataSourceNameAttribute.Value = \"DataSource1\"\n            $dataSourceReferenceNode.InnerText = \"DataSource1\"\n\n            # add attribute to datasource node\n            [void]$DataSourceNode.Attributes.Append($DataSourceNameAttribute)\n            [void]$DataSourceNode.AppendChild($DataSourceReferenceNode)\n\n            # add nodes\n            [void]$ReportNode.AppendChild($DataSourcesNode)\n            [void]$DataSourcesNode.AppendChild($DataSourceNode)\n\n            # add fake datasource name to array\n            $DataSourceNames += \"DataSource1\"\n        }\n\n        # get all datasource nodes\n        $DatasourceNodes = $ReportXml.GetElementsByTagName(\"DataSourceReference\");\n\n        # loop through returned nodes\n        foreach($DataSourceNode in $DatasourceNodes)\n        {\n            # create a new connection properties node\n            $ConnectionProperties = $ReportXml.CreateNode($DataSourceNode.NodeType, \"ConnectionProperties\", $null);\n\n            # create a new dataprovider node\n            $DataProvider = $ReportXml.CreateNode($DataSourceNode.NodeType, \"DataProvider\", $null);\n            $DataProvider.InnerText = \"SQL\";\n\n            # create new connection string node\n            $ConnectString = $ReportXml.CreateNode($DataSourceNode.NodeType, \"ConnectString\", $null);\n            $ConnectString.InnerText = \"Data Source=Server Name Here;Initial Catalog=database name here\";\n\n            # add new node to parent node\n            [void] $DataSourceNode.ParentNode.AppendChild($ConnectionProperties);\n\n            # append childeren\n            [void] $ConnectionProperties.AppendChild($DataProvider);\n            [void] $ConnectionProperties.AppendChild($ConnectString);\n\n            # Add to remove list \n            $NodesToRemove += $DataSourceNode;\n        }\n\n        # get all shareddataset nodes\n        $SharedDataSetNodes = $ReportXml.GetElementsByTagName(\"SharedDataSet\")\n\n        #loop through the returned nodes\n        foreach($SharedDataSetNode in $SharedDataSetNodes)\n        {\n            # create holder nodes so it won't error\n            $QueryNode = $ReportXml.CreateNode($SharedDataSetNode.NodeType, \"Query\", $null);\n            $DataSourceNameNode = $ReportXml.CreateNode($QueryNode.NodeType, \"DataSourceName\", $null);\n            $CommandTextNode = $ReportXml.CreateNode($QueryNode.NodeType, \"CommandText\", $null);\n\n            # add valid datasource name, just get the first in the list\n            $DataSourceNameNode.InnerText = $DataSourceNames[0]\n            \n            # add node to parent\n            [void] $SharedDataSetNode.ParentNode.Appendchild($QueryNode)\n            \n            # add datasourcename and commandtext to query node\n            [void]$QueryNode.AppendChild($DataSourceNameNode)\n            [void]$QueryNode.AppendChild($CommandTextNode)\n\n            # add node to removelist\n            $NodesToRemove += $SharedDataSetNode\n        }\n\n\n        # loop through nodes to remove\n        foreach($Node in $NodesToRemove)\n        {\n            # remove from parent\n            [void] $Node.ParentNode.RemoveChild($Node);\n        }\n    \n        $ReportXml.InnerXml = $ReportXml.InnerXml.Replace(\"xmlns=`\"`\"\", \"\")\n\n        # save altered xml to memory stream\n        $ReportXml.Save($MemoryStream);\n\n        # return the altered xml as byte array\n        return $MemoryStream.ToArray();\n    }\n    finally\n    {\n        # close and dispose\n        $MemoryStream.Close();\n        $MemoryStream.Dispose();\n    }\n}\n#endregion\n\n\n#region Copy-ObjectProperties()\nFunction Copy-ObjectProperties($SourceObject, $TargetObject)\n{\n    # Get source object property array\n    $SourcePropertyCollection = $SourceObject.GetType().GetProperties();\n\n    # get the destination\n    $TargetPropertyCollection = $TargetObject.GetType().GetProperties();\n\n    # loop through source property collection\n    for($i = 0; $i -lt $SourcePropertyCollection.Length; $i++)\n    {\n        # get the target property\n        $TargetProperty = $TargetPropertyCollection | Where {$_.Name -eq $SourcePropertyCollection[$i].Name}\n        \n        # check to see if it's null\n        if($TargetProperty -ne $null)\n        {\n            # check to see if it's the valid values property\n            if($TargetProperty.Name -ne \"ValidValues\")\n            {\n                 # set the value\n                $TargetProperty.SetValue($TargetObject, $SourcePropertyCollection[$i].GetValue($SourceObject));\n            }\n        }\n    }\n}\n#endregion\n\n#region ConvertValidValues()\nFunction Convert-ValidValues($SourceValidValues)\n{\n    # declare local values\n    $TargetValidValues = @();\n    \n    # loop through source values\n    foreach($SourceValidValue in $SourceValidValues)\n    {\n        # create target valid value object\n        $TargetValidValue = New-Object \"$ReportServerProxyNamespace.ValidValue\";\n\n        # copy properties\n        Copy-ObjectProperties -SourceObject $SourceValidValue -TargetObject $TargetValidValue\n\n        # add to list\n        $TargetValidValues += $TargetValidValue\n    }\n\n    # return the values\n    return ,$TargetValidValues\n}\n#endregion\n\n#region Backup-ExistingItem()\nFunction Backup-ExistingItem\n{\n    # parameters\n    Param($ItemFile, $ItemFolder)\n\n    # declare local variables\n    $ItemName = $ItemFile.SubString($ItemFile.LastIndexOf(\"\\\") + 1)\n    $ItemName = $ItemName.SubString(0, $ItemName.LastIndexOf(\".\"))\n\n    # check to see if the item exists\n    if((Item-Exists -ItemFolderPath $ItemFolder -ItemName $ItemName) -eq $true)\n    {\n        # get file extension\n        $FileExtension = [System.IO.Path]::GetExtension($ItemFile)\n    \n        # check backuplocation\n        if($BackupLocation.EndsWith(\"\\\") -ne $true)\n        {\n            # append ending slash\n            $BackupLocation = $BackupLocation + \"\\\"\n        }\n\t\t\n\t\t# add the release number to the backup location\n\t\t$BackupLocation = $BackupLocation + $ReleaseNumber + \"\\\"\n\n        # ensure the backup location actually exists\n        if((Test-Path $BackupLocation) -ne $true)\n        {\n            # create it\n            New-Item -ItemType Directory -Path $BackupLocation\n        }\n\n        # download the item\n        $Item = $ReportServerProxy.GetItemDefinition(\"$ItemFolder/$ItemName\")\n\n        # form the backup path\n        $BackupPath = \"{0}{1}{2}\" -f $BackupLocation, $ItemName, $FileExtension;\n\n        # write to disk\n        [System.IO.File]::WriteAllBytes(\"$BackupPath\", $Item);\n\n        # write to screen\n        Write-Host \"Backed up $ItemFolder/$ItemName to $BackupPath\";\n    }\n}\n#endregion\n\n#region Normalize-SSRSFolder()\nfunction Normalize-SSRSFolder ([string]$Folder) {\n    if (-not $Folder.StartsWith('/')) {\n        $Folder = '/' + $Folder\n    }\n    \n    return $Folder\n}\n#endregion\n\n#region New-SSRSFolder()\nfunction New-SSRSFolder ([string] $Name) {\n    Write-Verbose \"New-SSRSFolder -Name $Name\"\n \n    $Name = Normalize-SSRSFolder -Folder $Name\n \n    if ($ReportServerProxy.GetItemType($Name) -ne 'Folder') {\n        $Parts = $Name -split '/'\n        $Leaf = $Parts[-1]\n        $Parent = $Parts[0..($Parts.Length-2)] -join '/'\n \n        if ($Parent) {\n            New-SSRSFolder -Name $Parent\n        } else {\n            $Parent = '/'\n        }\n        \n        $ReportServerProxy.CreateFolder($Leaf, $Parent, $null)\n    }\n}\n#endregion\n\n#region Clear-SSRSFolder()\nfunction Clear-SSRSFolder ([string] $Name) {\n    Write-Verbose \"Clear-SSRSFolder -Name $Name\"\n    \n    $Name = Normalize-SSRSFolder -Folder $Name\n    \n    if ($ReportServerProxy.GetItemType($Name) -eq 'Folder' -and $ClearReportFolder) {\n        Write-Host (\"Clearing the {0} folder\" -f $Name)\n        $ReportServerProxy.ListChildren($Name, $false) | ForEach-Object {\n            Write-Verbose \"Deleting item: $($_.Path)\"\n            $ReportServerProxy.DeleteItem($_.Path)\n        }\n    }\n}\n#endregion\n\n#region New-SSRSDataSource()\nfunction New-SSRSDataSource ([string]$RdsPath, [string]$Folder, [bool]$OverwriteDataSources) {\n    Write-Verbose \"New-SSRSDataSource -RdsPath $RdsPath -Folder $Folder\"\n\n    $Folder = Normalize-SSRSFolder -Folder $Folder\n    \n    [xml]$Rds = Get-Content -Path $RdsPath\n    $dsName = $Rds.RptDataSource.Name\n    $ConnProps = $Rds.RptDataSource.ConnectionProperties\n    \n\t$type = $ReportServerProxy.GetType().Namespace #Get proxy type\n\t$DSDdatatype = ($type + '.DataSourceDefinition')\n\t \n\t$Definition = new-object ($DSDdatatype)\n\tif($Definition -eq $null){\n\t Write-Error Failed to create data source definition object\n\t}\n\t\n\t$dsConnectionString = $($OctopusParameters[\"$($dsName).ConnectionString\"])\n    $dsUsername = $($OctopusParameters[\"$($dsName).Username\"])\n    $dsPassword = $($OctopusParameters[\"$($dsName).Password\"])\n    $dsCredentialRetrieval = $($OctopusParameters[\"$($dsName).CredentialRetrieval\"])\n    \n\t# replace the connection string variable that is configured in the octopus project\n\tif ($dsConnectionString) {\n\t    $Definition.ConnectString = $dsConnectionString\n\t} else {\n\t    $Definition.ConnectString = $ConnProps.ConnectString\n\t}\n\t\n    $Definition.Extension = $ConnProps.Extension \n\n\t# Check to see if the credential retrieval is overridden\n    if ($null -ne $dsCredentialRetrieval)\n    {\n    \tWrite-Host \"Forcing CredentialRetrieval property to: $dsCredentialRetrieval.\"\n        $Definition.CredentialRetrieval = $dsCredentialRetrieval\n    }\n    else\n    {\n    \t# Set the Credential Retrieval method\n    \tif ([Convert]::ToBoolean($ConnProps.IntegratedSecurity)) {\n\t\t\t$Definition.CredentialRetrieval = 'Integrated'\n\t\t}\n        elseif (![string]::IsNullOrWhitespace($dsUsername) -and ![string]::IsNullOrWhitespace($dsPassword))\n        {\n        \t$Definition.CredentialRetrieval = 'Store'\n        }\n    }\n       \n\tif ($Definition.CredentialRetrieval -eq 'Store')\n    {\t\t\n\t\tWrite-Host \"$($dsName).Username = '$dsUsername'\"\n\t\tWrite-Host \"$($dsName).Password = '$dsPassword'\"\n\t\t\n\t\t$Definition.UserName = $dsUsername;\n        $Definition.Password = $dsPassword;\n\t}\n    \n    # Check to see if this is supposed to be an Windows Authentication stored account\n    if ($OctopusParameters[\"$($dsName).WindowsCredentials\"] -eq \"true\")\n    {\n\t    # Set the definition to Windows Credentials\n    \t$Definition.WindowsCredentials = $true\n    }\n    \n\n    $DataSource = New-Object -TypeName PSObject -Property @{\n        Name = $Rds.RptDataSource.Name\n        Path =  $Folder + '/' + $Rds.RptDataSource.Name\n    }\n    \n    if ($OverwriteDataSources -or $ReportServerProxy.GetItemType($DataSource.Path) -eq 'Unknown') {\n        Write-Host \"Overwriting datasource $($DataSource.Name)\"\n        $ReportServerProxy.CreateDataSource($DataSource.Name, $Folder, $OverwriteDataSources, $Definition, $null)\n    }\n    \n    return $DataSource \n}\n#endregion\n\n#region Main\n\ntry\n{\n    # declare array for reports\n    $ReportFiles = @()\n\t$ReportDataSourceFiles = @()\n    $ReportDataSetFiles = @()\n    $ReportPartFiles = @()\n\t\n\t$IsReportService2005 = $false\n\t$IsReportService2010 = $false\n\t\n\tif ($ReportServiceUrl.ToLower().Contains('reportservice2005.asmx')) {\n\t\t$IsReportService2005 = $true\n\t\tWrite-Host \"2005 Report Service found.\"\n\t}\n\telseif ($ReportServiceUrl.ToLower().Contains('reportservice2010.asmx')) {\n\t\t$IsReportService2010 = $true\n\t\tWrite-Host \"2010 Report Service found.\"\n\t}\n\t\n\tWrite-Host \"Deploy Path: $DeployedPath\"\n\t\n    # get all report files for deployment\n    Write-Host \"Getting all .rdl files\"\n    Get-ChildItem $DeployedPath -Recurse -Filter \"*.rdl\" | ForEach-Object { If(($ReportFiles -contains $_.FullName) -eq $false) {$ReportFiles += $_.FullName}}\n    Write-Host \"# of rdl files found: $($ReportFiles.Count)\"\n\n    # get all report datasource files for deployment\n    Write-Host \"Getting all .rds files\"\n    Get-ChildItem $DeployedPath -Recurse -Filter \"*.rds\" | ForEach-Object { If(($ReportDataSourceFiles -contains $_.FullName) -eq $false) {$ReportDataSourceFiles += $_.FullName}}\n    Write-Host \"# of rds files found: $($ReportDataSourceFiles.Count)\"\n\n    # get all report datset files for deployment\n    Write-Host \"Getting all .rsd files\"\n    Get-ChildItem $DeployedPath -Recurse -Filter \"*.rsd\" | ForEach-Object { If(($ReportDataSetFiles -contains $_.FullName) -eq $false) {$ReportDataSetFiles += $_.FullName}}\n    Write-Host \"# of rsd files found: $($ReportDataSetFiles.Count)\"\n\n    # get all report part files for deployment\n    Write-Host \"Getting all .rsc files\"\n    Get-ChildItem $DeployedPath -Recurse -Filter \"*.rsc\" | ForEach-Object { If(($ReportPartFiles -contains $_.FullName) -eq $false) {$ReportPartFiles += $_.FullName}}\n    Write-Host \"# of rsc files found: $($ReportPartFiles.Count)\"\n\n    # set the report proxies\n    Write-Host \"Creating SSRS Web Service proxies\"\n\n    # check to see if credentials were supplied for the services\n    if(([string]::IsNullOrEmpty($ServiceUserDomain) -ne $true) -and ([string]::IsNullOrEmpty($ServiceUserName) -ne $true) -and ([string]::IsNullOrEmpty($ServicePassword) -ne $true))\n    {\n        # secure the password\n        $secpasswd = ConvertTo-SecureString \"$ServicePassword\" -AsPlainText -Force\n\n        # create credential object\n        $ServiceCredential = New-Object System.Management.Automation.PSCredential (\"$ServiceUserDomain\\$ServiceUserName\", $secpasswd)\n\n        # create proxies\n        $ReportServerProxy = New-WebServiceProxy -Uri $ReportServiceUrl -Credential $ServiceCredential\n        $ReportExecutionProxy = New-WebServiceProxy -Uri $ReportExecutionUrl -Credential $ServiceCredential\n    }\n    else\n    {\n        # create proxies using current identity\n        $ReportServerProxy = New-WebServiceProxy -Uri $ReportServiceUrl -UseDefaultCredential \n        $ReportExecutionProxy = New-WebServiceProxy -Uri $ReportExecutionUrl -UseDefaultCredential \n    }\n\n\n\n\t#Create folder information for DataSource and Report\n    if ($UseArchiveStructure -eq $false)\n    {\n\t    New-SSRSFolder -Name $ReportFolder\n\t    New-SSRSFolder -Name $ReportDatasourceFolder\n        New-SSRSFolder -Name $ReportDataSetFolder\n        New-SSRSFolder -Name $ReportPartsFolder\n    }\n    else\n    {\n        New-SSRSFolder -Name $RootFolder\n    }\n    \n    #Clear destination folder if specified\n    if([System.Convert]::ToBoolean(\"$ClearReportFolder\")) {\n        Clear-SSRSFolder -Name $ReportFolder\n    }\n\t \n\t#Create DataSource\n    foreach($RDSFile in $ReportDataSourceFiles) {\n        Write-Host \"New-SSRSDataSource $RdsFile\"\n\n        $DatasourceFolder = \"\"\n\n        if ($UseArchiveStructure -eq $true)\n        {\n            # Adjust report folder to archive path\n            $DatasourceFolder = $(if ($RootFolder -eq \"/\") { [string]::Empty} else { $RootFolder } ) + $RDSFile.Replace($DeployedPath, '').Replace('\\', '/').Replace((Split-Path $RDSFile -Leaf), '')\n\n            # Remove final slash\n            $DatasourceFolder = $DatasourceFolder.Substring(0, $DatasourceFolder.LastIndexOf('/'))    \n            \n            # Check if folder exists\n            New-SSRSFolder -Name $DatasourceFolder                    \n        }\n        else\n        {\n            $DatasourceFolder = $ReportDatasourceFolder\n        }\n        \n\t\t$DataSource = New-SSRSDataSource -RdsPath $RdsFile -Folder $DatasourceFolder -Overwrite ([System.Convert]::ToBoolean(\"$OverwriteDataSources\"))\n\t}\n\n    # get the service proxy namespaces - this is necessary because of a bug documented here http://stackoverflow.com/questions/7921040/error-calling-reportingservice2005-finditems-specifically-concerning-the-bool and http://www.vistax64.com/powershell/273120-bug-when-using-namespace-parameter-new-webserviceproxy.html\n    $ReportServerProxyNamespace = Get-ObjectNamespace -Object $ReportServerProxy\n    $ReportExecutionProxyNamespace = Get-ObjectNamespace -Object $ReportExecutionProxy\n\n    # Create dat sets\n    foreach($DataSet in $ReportDataSetFiles)\n    {\n        $DataSetPath = \"\"\n\n        # Check to see if it's set to follow archive structure\n        if ($UseArchiveStructure -eq $true)\n        {\n            # Adjust report folder to archive path\n            $DataSetPath = $(if ($RootFolder -eq \"/\") { [string]::Empty} else { $RootFolder } ) + $DataSet.Replace($DeployedPath, '').Replace('\\', '/').Replace((Split-Path $DataSet -Leaf), '')\n\n            # Remove final slash\n            $DataSetPath = $DataSetPath.Substring(0, $DataSetPath.LastIndexOf('/'))\n\n            # Check if folder exists\n            New-SSRSFolder -Name $DataSetPath\n        }\n        else\n        {\n            $DataSetPath = $ReportDataSetFolder\n        }\n\n        # check to see if we need to back up\n        if($BackupLocation -ne $null -and $BackupLocation -ne \"\")\n        {\n            # backup the item\n            Backup-ExistingItem -ItemFile $DataSet -ItemFolder $DataSetPath\n        }\n\n        # upload the dataset\n        Upload-Item -Item $DataSet -ItemType \"DataSet\" -ItemFolder $DataSetPath\n\n        # update the dataset datasource\n        Set-ItemDataSources -ItemFile $DataSet -ItemFolder $DataSetPath\n    }\n\n    # get the proxy auto generated name spaces\n\n    # loop through array\n    foreach($ReportFile in $ReportFiles)\n    {\n        $ReportPath = \"\"\n\n        # Check to see if it's set to follow archive structure\n        if ($UseArchiveStructure -eq $true)\n        {\n            # Adjust report folder to archive path\n            $ReportPath = $(if ($RootFolder -eq \"/\") { [string]::Empty} else { $RootFolder } ) + $ReportFile.Replace($DeployedPath, '').Replace('\\', '/').Replace((Split-Path $ReportFile -Leaf), '')\n\n            # Remove final slash\n            $ReportPath = $ReportPath.Substring(0, $ReportPath.LastIndexOf('/'))\n\n            # Check if folder exists\n            New-SSRSFolder -Name $ReportPath\n        }\n        else\n        {\n            $ReportPath = $ReportFolder\n        }\n\n        # check to see if we need to back up\n        if($BackupLocation -ne $null -and $BackupLocation -ne \"\")\n        {\n            # backup the item\n            Backup-ExistingItem -ItemFile $ReportFile -ItemFolder $ReportPath\n        }\n        \n        \n        # upload report\n        Upload-Item -Item $ReportFile -ItemType \"Report\" -ItemFolder $ReportPath\n\n        # extract datasources\n        #Write-Host \"Extracting datasource names for $ReportFile\"\n        #$ReportDataSourceNames = Get-ReportDataSourceNames $ReportFile\n\n        # set the datasources\n        Set-ItemDataSources -ItemFile $ReportFile -ItemFolder $ReportPath\n\n        # set the datasets\n        Set-ReportDataSets -ReportFile $ReportFile -ReportFolderPath $ReportPath\n\n        # update the report parameters\n        Update-ReportParameters -ReportFile $ReportFile -ReportFolderPath $ReportPath\n    }\n    \n    # loop through rsc files\n    foreach($ReportPartFile in $ReportPartFiles)\n    {\n        # check to see if we need to back up\n        if($BackupLocation -ne $null -and $BackupLocation -ne \"\")\n        {\n            # backup the item\n            Backup-ExistingItem -ItemFile $ReportPartFile -ItemFolder $ReportPartsFolder\n        }\n\n\t\t# upload item\n        Upload-Item -Item $ReportPartFile -ItemType \"Component\" -ItemFolder $ReportPartsFolder\n    }\n    \n}\nfinally\n{\n    # check to see if the proxies are null\n    if($ReportServerProxy -ne $null)\n    {\n        # dispose\n        $ReportServerProxy.Dispose();\n    }\n\n    if($ReportExecutionProxy -ne $null)\n    {\n        # dispose\n        $ReportExecutionProxy.Dispose();\n    }\n}\n\n#endregion\n",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.RunOnServer": "false"
  },
  "Category": "SQL Server",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/ssrs-deploy-from-package.json",
  "Website": "/step-templates/4e3a1163-e157-4675-a60c-4dc569d14348",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Thursday, March 11, 2021