Open AlexAlex1337 opened 3 years ago
OK, I solved it and just deploy the reports and then use some functions I found via Google in combination with Pipeline Variables to update the datasource reference. Here is the code in case you want to implement something into your project.
function Global:Get-SSRS { [CmdletBinding()] param(
[Parameter(Mandatory=$true)]
[string] $ReportServerUri
)
[string] $fn = $MyInvocation.MyCommand
[string] $stepName = "Begin [$fn]"
[string] $reportServerURIOverride = $ReportServerUri
[object] $returnObj = $null
try
{
$stepName = "[$fn]: Validate parameters"
#--------------------------------------------
Write-Verbose $stepName
#Append the "?wsdl" at the end if it does not already have it
# e.g., 'http://MYSERVER/MYINSTReportServer/ReportService2010.asmx?wsdl'
if (-not $reportServerURIOverride.EndsWith('?wsdl'))
{
$reportServerURIOverride = ("$reportServerURIOverride"+'?wsdl')
}
$stepName = "[$fn]: Open SSRS url"
#--------------------------------------------
Write-Verbose $stepName
$URI = New-Object System.Uri($ReportServerUri)
$SSRS = New-WebServiceProxy -Uri $URI -UseDefaultCredential
$returnObj = $SSRS
#Return value
,$returnObj
}
catch
{
[Exception]$ex = $_.Exception
Throw "Unable to get SSRS reference. Error in step: `"{0}]`" `n{1}" -f `
$stepName, $ex.Message
}
finally
{
#Return value if any
}
}
function Global:Get-SSRSReport { [CmdletBinding()] param(
[Parameter(Mandatory=$true)]
[string] $ReportServerUri,
[Parameter(Mandatory=$false)]
[string] $ReportFolderPath = '/',
[Parameter(Mandatory=$false)]
[string] $ReportName = ''
)
[string] $fn = $MyInvocation.MyCommand
[string] $stepName = "Begin [$fn]"
#-----------------------------------
#We need to collect all these inputs!
#-----------------------------------
[object[]] $returnObj = @()
try
{
$stepName = "[$fn]: Validate parameters"
#--------------------------------------------
Write-Verbose $stepName
$stepName = "[$fn]: Open SSRS url"
#--------------------------------------------
Write-Verbose $stepName
$SSRS = Get-SSRS -ReportServerUri $ReportServerUri
$stepName = "[$fn]: Get all the reports"
#--------------------------------------------
Write-Verbose $stepName
$reports = $SSRS.ListChildren($ReportFolderPath, $true) |
Where-Object {$_.TypeName -eq 'Report'}
#Result will be of the form (first element):
<# $reports[0] ID : 0b767817-1c39-4a86-98e5-ab372f80dc64 Name : MyReport Path : /MyApp/MyReport VirtualPath : TypeName : Folder Size : 0 SizeSpecified : False Description : Hidden : False HiddenSpecified : False CreationDate : 1/14/2015 11:11:43 AM CreationDateSpecified : True ModifiedDate : 1/14/2015 11:11:59 AM ModifiedDateSpecified : True CreatedBy : MYUSERID ModifiedBy : MYUSERID ItemMetadata : {} #>
$stepName = "[$fn]: Filter to specific report among the reports"
#--------------------------------------------
Write-Verbose $stepName
if ($ReportName.Trim().Length -gt 0)
{
<# #Combine the folder and the report name to form full path (without the file extension if any) $reportFullPath = Join-Path ` -Path $ReportFolderPath ` -ChildPath ([system.io.fileinfo]$ReportName).BaseName #>
#Not doing it with the full path would allow us to search all reports in all paths by name starting from the root!
$report = ($reports |
Where-Object {$_.Name -eq ([system.io.fileinfo]$ReportName).BaseName}) #Eliminate file extension
$returnObj = @($report)
}
else
{
$returnObj = $reports
}
#Return value
,$returnObj
}
catch
{
[Exception]$ex = $_.Exception
Throw "Unable to get SSRS reports. Error in step: `"{0}]`" `n{1}" -f `
$stepName, $ex.Message
}
finally
{
#Return value if any
}
}
function Global:Get-SSRSSharedDataSource { [CmdletBinding()] param(
[Parameter(Mandatory=$true)]
[string] $ReportServerUri,
[Parameter(Mandatory=$false)]
[string] $DataSourcePath = '/',
[Parameter(Mandatory=$false)]
[string] $DataSourceName = ''
)
[string] $fn = $MyInvocation.MyCommand
[string] $stepName = "Begin [$fn]"
#-----------------------------------
#We need to collect all these inputs!
#-----------------------------------
[string] $dataSourcePathOverride = $DataSourcePath
[object[]] $returnObj = @()
try
{
$stepName = "[$fn]: Validate parameters"
#--------------------------------------------
Write-Verbose $stepName
$stepName = "[$fn]: Open SSRS url"
#--------------------------------------------
Write-Verbose $stepName
$SSRS = Get-SSRS -ReportServerUri $ReportServerUri
$stepName = "[$fn]: Get all the datasources"
#--------------------------------------------
Write-Verbose $stepName
if ($dataSourcePathOverride.Trim().Length -eq 0)
{
$dataSourcePathOverride = '/' #Could use "/Data Sources" but there could be data sources in other locations!
}
#Get the datasources
$dataSources = $SSRS.ListChildren($dataSourcePathOverride, $true) |
Where-Object {$_.TypeName -eq 'DataSource'}
#$dataSources = $SSRS.GetItemDataSources($dataSourcePathOverride)
#Result will be of the form (first element):
<# $DataSources[0] ID : b58a4feb-c3ad-49c8-a699-a4f52885d09d Name : My DataSourceName Path : /Data Sources/MyDataSourceName VirtualPath : TypeName : DataSource Size : 467 SizeSpecified : True Description : Hidden : False HiddenSpecified : False CreationDate : 1/14/2015 11:11:43 AM CreationDateSpecified : True ModifiedDate : 2/25/2015 12:14:16 PM ModifiedDateSpecified : True CreatedBy : domain\SOMEUSER ModifiedBy : domain\SOMEUSER ItemMetadata : {} #>
$stepName = "[$fn]: Filter to specific datasource if one was asked for"
#--------------------------------------------
Write-Verbose $stepName
if ($DataSourceName.Trim().Length -gt 0)
{
$dataSource = ($dataSources |
Where-Object {$_.Name -eq $DataSourceName})
$returnObj = @($dataSource)
}
else
{
$returnObj = $dataSources
}
#Return value
,$returnObj
}
catch
{
[Exception]$ex = $_.Exception
Throw "Unable to get SSRS data sources. Error in step: `"{0}]`" `n{1}" -f `
$stepName, $ex.Message
}
finally
{
#Return value if any
}
}
function Global:Set-SSRSReportDataSource { [CmdletBinding()] param(
[Parameter(Mandatory=$true)]
[string] $ReportServerUri,
[Parameter(Mandatory=$false)]
[string] $ReportFolderPath = '/',
[Parameter(Mandatory=$true)]
[string] $ReportName,
[Parameter(Mandatory=$false)]
[string] $DataSourcePath = '/',
[Parameter(Mandatory=$true)]
[string] $DataSourceName
)
[string] $fn = $MyInvocation.MyCommand
[string] $stepName = "Begin [$fn]"
[HashTable]$params = @{
'ReportServerUri' = $ReportServerUri;
'ReportFolderPath' = $ReportFolderPath;
'ReportName' = $ReportName;
'DataSourcePath' = $DataSourcePath;
'DataSourceName' = $DataSourceName}
#-----------------------------------
#We need to collect all these inputs!
#-----------------------------------
[string] $reportServerURIOverride = $ReportServerUri
try
{
$stepName = "[$fn]: Validate parameters"
#--------------------------------------------
Write-Verbose $stepName
$stepName = "[$fn]: Get data source: [{0}]" -f $DataSourceName
#--------------------------------------------
Write-Verbose $stepName
$dataSource = Get-SSRSSharedDataSource `
-ReportServerUri $ReportServerUri `
-DataSourcePath $DataSourcePath `
-DataSourceName $DataSourceName
if (($dataSource.Count -eq 0) `
-or ($dataSource.Count -gt 1))
{
Throw "[$fn]: Found no matches or multiple data source matches for parameters: [{0}]" -f ($params | Out-String)
}
$stepName = "[$fn]: Get report"
#--------------------------------------------
Write-Verbose $stepName
$report = Get-SSRSReport `
-ReportServerUri $ReportServerUri `
-ReportFolderPath $ReportFolderPath `
-ReportName $ReportName
if (($report.Count -eq 0) `
-or ($report.Count -gt 1))
{
Throw "[$fn]: Found no matches or multiple report matches for parameters: [{0}]" -f ($params | Out-String)
}
$stepName = "[$fn]: Open SSRS url"
#--------------------------------------------
Write-Verbose $stepName
$SSRS = Get-SSRS -ReportServerUri $ReportServerUri
$stepName = "[$fn]: Update the data source"
#--------------------------------------------
Write-Verbose $stepName
#https://blogs.infosupport.com/managing-ssrs-reports-with-powershell/
#http://powershell-with-dave.blogspot.com/2014/08/editing-ssrs-shared-data-sources-with.html
#The most useful link that finally helped me make this work!
#http://stackoverflow.com/questions/9178685/change-datasource-of-ssrs-report-with-powershell
$dataSources = $SSRS.GetItemDataSources($report[0].Path)
$dataSources | ForEach-Object {
$proxyNamespace = $SSRS.GetType().Namespace
$dataSourceNew = New-Object ("$proxyNamespace.DataSource") #New-Object SSRS.DataSource
$dataSourceNew.Name = $dataSource[0].Name
$dataSourceNew.Item = New-Object ("$proxyNamespace.DataSourceReference") #New-Object SSRS.DataSourceReference
$dataSourceNew.Item.Reference = $dataSource[0].Path
$_.Item = $dataSourceNew.Item
$SSRS.SetItemDataSources($report[0].Path, $_)
#$SSRS.SetItemDataSources($report[0].Path, @($dataSourceNew))
}
<# #The above method did not work initially so tried this but this did not work either! Left here for reference in case we need to more things! #http://stackoverflow.com/questions/36334676/deploying-ssrs-rdl-files-from-vb-net-issue-with-shared-datasources?rq=1 $itemRef = New-Object ("$proxyNamespace.ItemReference") $itemRef.Name = $dataSource[0].Name $itemRef.Reference = $dataSource[0].Path $SSRS.SetItemReferences($report[0].Path, @($itemRef)) #>
#Return value
}
catch
{
[Exception]$ex = $_.Exception
Throw "Unable to set SSRS report data source. Error in step: `"{0}]`" `n{1}" -f `
$stepName, $ex.Message
}
finally
{
#Return value if any
}
}
Set-SSRSReportDataSource `
-ReportServerUri 'https://$(PBIRS)/ReportServer/ReportService2010.asmx' `
-ReportFolderPath '$(ReportFolderPath)' `
-ReportName '$(ReportName)' `
-DataSourcePath '$(DataSourcePath)' `
-DataSourceName '$(DataSourceName)'
Hi Alex
What I have found is that deploying an update to an existing report is fine anyway - it retains whatever datasource linking it already has. So this is only an issue for new reports.
That script does the job well for new reports so thanks for that. I think I will look to build it into my pipeline release by putting it into a standard-named file in the project with the relevant values for the relevant commit - we already have to do that for the extension's deployment configuration json file.
It would be really great if the extension itself could link up datasources like this though. I have used SSRS at two sites, and both have their datasources defined independently on the server and not in the project.
Thanks again for the script.
Steve
Dear author mmajcica,
I have several reports that use the same Shared Datasource and two environments (TEST/PROD).
The Shared Datasource is called ODS1 and uses Windows Credentials with Username/PWD saved in the Datasource. TEST and PROD environment have the ODS1 in the same location, but use different Connection Strings and Username/PWD.
The idea is that these ODS1 are manually created in TEST/PROD environment and should not be changed or overwritten by the DevOps Pipelines. Reports should be deployed/overwritten by DevOps pipelines to both environments.
At the moment it seems that this is not possible. When I just have the reports in the configuration json, they get deployed but the Datasource is not referenced and that needs to be manually done after deployment. When I have the datasource and the reports in the configuration json, then the datasource gets properly referenced in the reports which is fine if you have just one environment, but at the moment it creates the Datasource on PROD with the Connectionstring/User/PWD of the TEST environment.
Is there an option to NOT overwrite existing Datasources, but overwrite existing reports and still reference the Datasources in the reports after deploying? The Datasources are named identically in both environments and are at the same path, so that should not be an issue.
best regards Alex