Comments (6)
Certainly! Let me see if I can do some debugging on my end. To make sure I understand the issue correctly, you have a report that has two datasets; we’ll call them DataSetA and DataSetB. Each dataset uses a different datasource, DataSourceA and DatasourceB. The report is configured like DataSetA -> DataSourceA and DataSetB -> DataSetB. However, when the report gets deployed, DataSetA -> DataSourceB and DataSetB -> DataSourceA?
From: Ronnie Overby [mailto:[email protected]]
Sent: Thursday, July 23, 2015 7:04 AM
To: OctopusDeploy/Library [email protected]
Cc: Sesna, Shawn (DEL) [email protected]
Subject: [Library] Problem with community step template: Deploy SSRS Reports from a package (#190)
@twerthihttps://github.com/twerthi Thanks for the great step template (#150#150)
I found an issue with it that I’m trying to fix, but maybe you can help out.
When deploying a report with 2 datasets that each target a different shared data source, the targeted data sources get swapped:
[image]https://cloud.githubusercontent.com/assets/101028/8852255/fbbce33e-3121-11e5-8e2b-c564be2f38b1.png
I think the problem is in the script function Set-ReportDataSources, but I'm weak with powershell, so it's taking me some effort to debug it. Can you lend a hand?
—
Reply to this email directly or view it on GitHubhttps://github.com//issues/190.
from library.
Yes, you seem to understand the problem exactly. I did get powershell ISE debugger working and stepped through the script and did verify that the problem is inside the loop in the function called Set-ReportDataSources
. The code is not ensuring that the names match, nor is it ordering both collections before setting the datasource.
The best fix in my opinion is to look inside the .rdl xml and for the name of the correct datasource to use. Here's an example of another script I found that does just that: https://github.com/timabell/ssrs-powershell-deploy/blob/master/Deploy-SSRSProject/Deploy-SSRSProject.ps1#L220
from library.
Nice catch! I’ve been using this logic for years and haven’t encountered that issue, but I see what you’re talking about and it is an issue. I attempted to duplicate the problem, but can’t seem to get it to do what you’re seeing. Would it be possible to send me the RDL so that I can ensure that the issue is resolved before declaring it fixed?
From: Ronnie Overby [mailto:[email protected]]
Sent: Thursday, July 23, 2015 11:06 AM
To: OctopusDeploy/Library [email protected]
Cc: Sesna, Shawn (DEL) [email protected]
Subject: Re: [Library] Problem with community step template: Deploy SSRS Reports from a package (#190)
Yes, you seem to understand the problem exactly. I did get powershell ISE debugger working and stepped through the script and did verify that the problem is inside the loop in the function called Set-ReportDataSources. The code is not ensuring that the names match, nor is it ordering both collections before setting the datasource.
The best fix in my opinion is to look inside the .rdl xml and for the name of the correct datasource to use. Here's an example of another script I found that does just that: https://github.com/timabell/ssrs-powershell-deploy/blob/master/Deploy-SSRSProject/Deploy-SSRSProject.ps1#L220
—
Reply to this email directly or view it on GitHubhttps://github.com//issues/190#issuecomment-124189036.
from library.
You actually exposed two bugs, I found that the Item-Exists method would report a false positive if the item you were looking for exists in a subfolder. I also found the extraction of the datasource names by reading the XML directly was completely unnecessary as the Web Service method of GetItemDataSources gives you the same information. I now check to make sure that datasource name exists in the specified data source location and then create the new reference.
Since you have the script running in the ISE, would you be able to test this new version to see if it fixes the issue?
Param([string] $DeployedPath)
########################################
DEBUG SECTION
$DeployedPath = "c:\temp\reports"
$ReportServerUrl = "http://tools.dev.del.wa.gov/ReportServer_LOCALDEV/ReportService2010.asmx?wsdl"
$ReportExecutionUrl = "http://tools.dev.del.wa.gov/ReportServer_LOCALDEV/ReportExecution2005.asmx?wsdl"
$ReportFolder = "/ShawnTemp"
$ReportDatasourceFolder = "/ShawnTemp/DataSources"
$BackupLocation = "c:\temp\reportbackups"
########################################
#region Upload-Report()
Function Upload-Report([string] $ReportFile)
{
Write-Host "Loading binary data from $ReportFile"
$ReportData = [System.IO.File]::ReadAllBytes($ReportFile)
# Create local variables
$ReportServiceWarnings = $null
$ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("\") + 1)
$ReportName = $ReportName.SubString(0, $ReportName.IndexOf("."))
# Upload report
Write-Host "Uploading $ReportName"
$Result = $ReportServerProxy.CreateCatalogItem("Report", $ReportName, $ReportFolder, $true, $ReportData, $null, [ref] $ReportServiceWarnings);
}
#endregion
#region Get-ReportDataSourceNames()
Function Get-ReportDataSourceNames([string] $ReportFile)
{
# declare working variables
$DataSourceNames = @()
# load the xml
[xml]$ReportXml = Get-Content $ReportFile
# retrieve the datasource nodes
$DataSourceReferenceNodes = $ReportXml.GetElementsByTagName("DataSourceReference")
# loop through returned results
foreach($Node in $DataSourceReferenceNodes)
{
# store the name
$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 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"
$SearchCondition.Values = @($ItemName)
# search
$items = $ReportServerProxy.FindItems($ItemFolderPath, (Get-SpecificEnumValue -EnumNamespace "$ReportServerProxyNamespace.BooleanOperatorEnum" -EnumName "And"), $null, $SearchCondition)
# check to see if anything was returned
if($items.Length -gt 0)
{
# return true
#return $true
# loop through returned items
foreach($item in $items)
{
# check the path
if($item.Path -eq "$ItemFolderPath/$ItemName")
{
# return true
return $true
}
}
# items were found, but the path doesn't match
return $false
}
else
{
return $false
}
}
#endregion
#region Set-ReportDataSources()
Function Set-ReportDataSources($ReportFile)
{
# declare local variables
$ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("") + 1)
$ReportName = $ReportName.SubString(0, $ReportName.IndexOf("."))
$AllDataSourcesFound = $true
# get the datasources
$DataSources = $ReportServerProxy.GetItemDataSources([string]::Format("{0}/{1}", $ReportFolder, $ReportName))
#loop through retrieved datasources
foreach($DataSource in $DataSources)
{
# check to make sure the datasource exists in the location specified
if((Item-Exists -ItemFolderPath $ReportDatasourceFolder -ItemName $DataSource.Name) -eq $true)
{
# create datasource reference variable
$DataSourceReference = New-Object "$ReportServerProxyNamespace.DataSourceReference";
# assign
$DataSourceReference.Reference = "$ReportDatasourceFolder/" + $DataSource.Name
$DataSource.Item = $DataSourceReference
}
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.
$DataSourceName = $DataSource.Name
# display warning
Write-Warning "Warning - unable to find datasource $DataSourceName in $ReportDataSourceFolder"
# update to false
$AllDataSourcesFound = $false
}
}
# check to see if found all datasources
if($AllDataSourcesFound -eq $true)
{
Write-Host "Associating datasources to $ReportFolder/$ReportName"
# save the references
$ReportServerProxy.SetItemDataSources("$ReportFolder/$ReportName", $DataSources)
}
}
#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)
{
# 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-Datasources -ReportFile $ReportFile)
# get just the report name
$ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("\") + 1)
$ReportName = $ReportName.SubString(0, $ReportName.IndexOf("."))
# create warnings object
$ReportExecutionWarnings = $null
# 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;
# add to list
$ReportParameters += $ItemParameter;
}
# force the parameters to update
Write-Host "Updating report parameters for $ReportFolder/$ReportName"
$ReportServerProxy.SetItemParameters("$ReportFolder/$ReportName", $ReportParameters);
}
#endregion
#region Remove-Datasources()
Function Remove-Datasources($ReportFile)
{
######################################################################################################
#You'll notice that I've used the keywrod 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;
# create new memory stream object
$MemoryStream = New-Object System.IO.MemoryStream
try
{
# get all datasource nodes
$DatasourceNodes = $ReportXml.GetElementsByTagName("DataSourceReference");
$NodesToRemove = @();
# 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;
}
# 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($ReportFile)
{
# declare local variables
$ReportName = $ReportFile.SubString($ReportFile.LastIndexOf("") + 1)
$ReportName = $ReportName.SubString(0, $ReportName.IndexOf("."))
# check to see if the item exists
if((Item-Exists -ItemFolderPath $ReportFolder -ItemName $ReportName) -eq $true)
{
# get file extension
$ReportExtension = [System.IO.Path]::GetExtension($ReportFile)
# check backuplocation
if($BackupLocation.EndsWith("\") -ne $true)
{
# append ending slash
$BackupLocation = $BackupLocation + "\"
}
# 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("$ReportFolder/$ReportName")
# form the backup path
$BackupPath = "{0}{1}{2}" -f $BackupLocation, $ReportName, $ReportExtension;
# write to disk
[System.IO.File]::WriteAllBytes("$BackupPath", $Item);
# write to screen
Write-Host "Backed up $ReportFolder/$ReportName to $BackupPath";
}
}
#endregion
#region Main
try
{
# declare array for reports
$ReportFiles = @()
# 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}}
# set the report proxies
Write-Host "Creating SSRS Web Service proxies"
$ReportServerProxy = New-WebServiceProxy -Uri $ReportServerUrl -UseDefaultCredential
$ReportExecutionProxy = New-WebServiceProxy -Uri $ReportExecutionUrl -UseDefaultCredential
# 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
# get the proxy auto generated name spaces
# loop through array
foreach($ReportFile in $ReportFiles)
{
# check to see if we need to back up
if($BackupLocation -ne $null -and $BackupLocation -ne "")
{
# backup the item
Backup-ExistingItem -ReportFile $ReportFile
}
# upload report
Upload-Report $ReportFile
# extract datasources
#Write-Host "Extracting datasource names for $ReportFile"
#$ReportDataSourceNames = Get-ReportDataSourceNames $ReportFile
# set the datasources
#Set-ReportDataSources -ReportDataSourceNames $ReportDataSourceNames -ReportFile $ReportFile
Set-ReportDataSources -ReportFile $ReportFile
# update the report parameters
Update-ReportParameters -ReportFile $ReportFile
}
}
finally
{
# check to see if the proxies are null
if($ReportServerProxy -ne $null)
{
# dispose
$ReportServerProxy.Dispose();
}
if($ReportExecutionProxy -ne $null)
{
# dispose
$ReportExecutionProxy.Dispose();
}
}
#endregion
From: Ronnie Overby [mailto:[email protected]]
Sent: Thursday, July 23, 2015 11:06 AM
To: OctopusDeploy/Library [email protected]
Cc: Sesna, Shawn (DEL) [email protected]
Subject: Re: [Library] Problem with community step template: Deploy SSRS Reports from a package (#190)
Yes, you seem to understand the problem exactly. I did get powershell ISE debugger working and stepped through the script and did verify that the problem is inside the loop in the function called Set-ReportDataSources. The code is not ensuring that the names match, nor is it ordering both collections before setting the datasource.
The best fix in my opinion is to look inside the .rdl xml and for the name of the correct datasource to use. Here's an example of another script I found that does just that: https://github.com/timabell/ssrs-powershell-deploy/blob/master/Deploy-SSRSProject/Deploy-SSRSProject.ps1#L220
—
Reply to this email directly or view it on GitHubhttps://github.com//issues/190#issuecomment-124189036.
from library.
Did you have an opportunity to test the new version?
from library.
Unfortunately, no. I've been "redirected" to another project :) I'll try to follow up soon. Thanks again for the speedy response!
from library.
Related Issues (20)
- Question: How to Include Packages HOT 1
- Unable to install Kubernetes - Create Service Account and Target step template HOT 4
- [Feature Request] Annotations on K8s namespace HOT 3
- Chain Deployment step template fails to create a new release of a project HOT 6
- getting commit list into step powershell code. HOT 1
- Add support for TLS 1.2 for Teams - Post a message step template HOT 2
- Windows Scheduled Task step fails if password contains double quote HOT 1
- Create Database Release (Worker Friendly) does not work with vanilla NuGet packages HOT 6
- Azure Logo HOT 1
- Repository - support Libraries of Departmental ALIA HOT 1
- Slack Detailed Notification failed if the release notes includes some special characters like & or | HOT 5
- GCP Secret Manager - Retrieve Secrets - Add option to ignore warning when retrieving latest version of secret HOT 2
- Upcoming major releases of Chocolatey products that may impact functionality on the Chocolatey step templates HOT 7
- Command Timeout parameter has no effect in SQL - Deploy DACPAC using SqlPackage step template HOT 2
- The service cannot accept control messages at this time HOT 2
- Issue with Microsoft Teams - Post a message HOT 6
- Issue with Git - Pull (HTTPS)
- SQL - Deploy DACPAC using SqlPackage - Lost functionality when updating from legacy package HOT 10
- Issue with Run Azure DevOps Build Pipeline HOT 2
- Issue with Run Azure DevOps Build Pipeline
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from library.