Git Product home page Git Product logo

Comments (6)

twerthi avatar twerthi commented on June 19, 2024

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.

ronnieoverby avatar ronnieoverby commented on June 19, 2024

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.

twerthi avatar twerthi commented on June 19, 2024

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.

twerthi avatar twerthi commented on June 19, 2024

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.

twerthi avatar twerthi commented on June 19, 2024

Did you have an opportunity to test the new version?

from library.

ronnieoverby avatar ronnieoverby commented on June 19, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.