Thursday, June 21, 2012

Setting a Default Selection on a PerformancePoint Scorecard


Depending on the design of your dashboard, you may want to set a default selection on a PerformancePoint Scorecard that is used as a filter for other web parts on a dashboard page.
As an example, consider a dashboard that contains a PerformancePoint Filter, Scorecard and an Analytic Report (Grid or Chart). The filter is used to filter the scorecard and the report. Additionally, the scorecard is used to filter the report based on a row member connection. The filter contains a list of manufacturers, the scorecard provides KPI for a list of products a manufacturer carries and the report displays the count of a selected manufacturer’s product sold by state. “Manufacturer 1” sells products A, B and C and “Manufacturer 2” sells products B, C and D.

One approach is to set the default of the Analytic Report to something that could make sense without a Scorecard filter being applied. By selecting default Members for the Analytic Report in Dashboard Designer, the dashboard defaults to displaying information, even without a scorecard selection. Depending on the complexity of your dashboard, the data, etc. maybe this is acceptable (this approach did not make sense for my particular dashboards).
Another approach is to select a Member (e.g. “Unknown” product that no manufacturer carries) that won’t produce any data in the Analytic Report. The resulting chart would look something like the following (note, I’m using a PerformancePoint Stack Selector, hence the web part title within a dropdown box):



Not great, but at least we aren’t misleading the dashboard user. I initially took this approach and went so far as to add a message in any PerformancePoint Reporting Services (i.e. SSRS report) web parts that were connected to the scorecard telling the user that they had to select a product (i.e. row) in the scorecard, if one wasn’t selected.
In the end, I wasn’t satisfied with this approach either, so I went Googling and stumbled across this question and response (http://stackoverflow.com/questions/3372565/performancepoint-sharepoint-2010-and-jquery) which turned up the NotifyBrowserOfAsyncUpdate event. This event is triggered for every PerformancePoint web part that is updated on a page. By using some jQuery and by identifying some PerformancePoint CSS classes, we can determine when the scorecard has been updated and whether or not we need to select a default row member by triggering a cell click event. I use the function below in an external script for use with multiple dashboards (I’ll leave it to you to streamline the jQuery as I have attempted to make it more understandable by adding comments and removing additional functionality).

function NotifyBrowserOfAsyncUpdate(elem) {
    var elemId, selector, rows, row;
    // id of web part being updated
    elemId = $(elem).prop('id');
    // .sctb is a PerformancePoint Scorecard class
    selector = '#' + elemId + " table.sctb";

    if ($(selector).length != 0) {
        // scorecard web part found! get rows that have a class that begins with "r-" (i.e. class|="r")
        rows = $(selector).find('tr[class|="r"]');
        // .scs is a PerformancePoint Scorecard’s selected row class
        if ($(rows).find('.scs').size() == 0) {
            row = $(rows)[0];
            // find head cell in first row and click
            if ($(row).children('th').size() != 0 && $(row).children('th')[0].length != 0) {
                $(row).children('th')[0].click();
            }
        }
    }
}


Because the cell click event is only triggered when a scorecard row is not selected, this will usually only happen when the page loads for the first time. This event would also be triggered when changing, for example, the manufacturer filter from “Manufacturer 1” with “Product A” selected in the scorecard to “Manufacturer 2”. As “Manufacturer 2” does not produce “Product A”, the click event is triggered and “Product B” would be the default scorecard selection.

Edit: June 14, 2014

When drilling up on a Scorecard from a root item that is not the first item, the "elem" element will be undefined and therefore a default row member will not get selected. In this case, which is likely to be infrequent, you can just use "table.sctb" as the selector. The revised function is provided below:

function NotifyBrowserOfAsyncUpdate(elem) {
    var elemId, selector, rows, row;

    if (typeof elem === 'undefined') {
        // occurs when drilling up to All in scorecard
        selector = "table.sctb";
    }
    else {
        elemId = $(elem).prop('id');
        selector = '#' + elemId + " table.sctb";
    }

    if ($(selector).length != 0) {
        // scorecard element found! get rows that have class that begin with "r-" (i.e. class|="r")
        rows = $(selector).find('tr[class|="r"]');
        if ($(rows).find('.scs').size() == 0) {
            row = $(rows)[0];
            // find head cell in first row and click
            if ($(row).children('th').size() != 0 && $(row).children('th')[0].length != 0) {
                $(row).children('th')[0].click();
            }
        }
    }
}

Wednesday, May 30, 2012

Breadcrumb Bar Style Dashboard Navigation

I recently needed to provide a breadcrumb on a SharePoint dashboard page which uses web parts to provide information about a sales territory area. Because the sales territories are defined as a hierarchy consisting of thousands of areas (e.g. regions which contain districts, districts which contain trade areas, and trade areas which contain counties), I chose to use a Query String (URL) Filter to pass an area to the dashboard page, rather than trying to load the areas into a PerformancePoint tree filter or multiple cascading filters. As a result, the page is much more light weight and easy to navigate, however, at the expense of having to do full postbacks to navigate between areas.

Conceptually, the area breadcrumb needed to show the area being viewed and the path to that area (i.e. all of its parents). Additionally, the parents could be links, providing a way to navigate to any of the parent areas. A very simplistic approach could display the information in a manner similar to the following:

National > Region 1 > District 1D > Trade Area 1D2 > Orange County, CA

But then I thought of the Breadcrumb Bar that was originally introduced with the Windows Vista version of Windows Explorer. These aren't drives, folders and files, but the hierarchical nature of the sales territories fit perfectly. The benefit of this approach would be to allow additional navigation possibilities to any of an area's parent's children (read on if that's not clear).

With a bit of Googling, I found a nice basis for my breadcrumb bar with this example: CSS-Only Dropdown Menu. It basically provides a very simple, yet elegant, drop down menu with a little javascript to handle the mouseover and mouseout events.

Breadcrumb Bar Example for Orange County, CA (this example should be interactive... at least it was when I originally posted it):




After dummying up an example of how I wanted the breadcrumb bar to look, I then had to decide how I would need to format the data to populate such a control. I came up with the following which required a stored procedure that basically has to determine each of the selected area's parents and the children of each of those parents (you'll have to design your own query based on your database design). Again, the following is what would be needed when passing "Orange County, CA":



AreaName
AreaType
AreaLevel
IsSelected
National
National
1
1
Region 1
Regions
2
0
Region 2
Regions
2
1
District 1A
Districts
3
0
District 1B
Districts
3
0
District 1C
Districts
3
0
District 1D
Districts
3
1
District 1E
Districts
3
0
District 1F
Districts
3
0
Trade Area 1D1
Trade Areas
4
0
Trade Area 1D2
Trade Areas
4
1
Trade Area 1D3
Trade Areas
4
0
Trade Area 1D4
Trade Areas
4
0
Los Angeles County, CA
Counties
5
0
Orange County, CA
Counties
5
1
Santa Barbara County, CA
Counties
5
0
Ventura County, CA
Counties
5
0


As an example, if I pass "District 1D" to the stored procedure, it would return "Region 2" and "National" (i.e. the parents of "District 1D") and "Region 1" (the additional child of "National") and the other children Districts of "Region 2".

Finally, I needed to build the control. I could have easily written a custom web part, but opted for a Data Form Web part instead. This basically required a bit of XSL to build the HTML. Again, I'll leave that exercise to you, the reader.

The final control also included an "enabled" flag that would allow me display the hierarchy, but disable parent areas that users did not have access to. This required an additional argument to the stored procedure of the user accessing the dashboard.

Although this post is light on code, I hope that it may help in creating your own breadcrumb bar for the navigation of a PerformancePoint dashboard.

Sunday, May 20, 2012

SharePoint 2010, SSRS 2012 and Internet Explorer 9 Issues

In SharePoint 2010, when using the RSViewerPage.aspx to view SSRS reports in Internet Explorer 9, you may experience the following problems:
  • Increasing the Zoom percentage to more than 100% on the page’s toolbar results in the top of the report not being visible. Decreasing the Zoom percentage below 100% results in extra whitespace at the top of the report.
  • The Actions->Export flyout menu does not appear.
I have additionally experienced a problem with a PerformancePoint Reporting Services web part that is displayed in a modal dialog called using SharePoint’s SP.UI.ModalDialog.showModalDialog method. When I click a button on the page that calls the method and displays the dialog, the report will update itself based on connections to filters on the page. Works great in IE7, IE8, Chrome and FireFox, however in IE9, the update to the report is only triggered in compatibility mode.

Adding your site to IE9’s Compatibility View settings list (Tools->Compatibility View settings) should fix the functionality. After discovering this workaround on my own, I stumbled across the following Microsoft Knowledge Base article which addresses the Export menu issue:
FIX: SSRS 2008 R2 report or SSRS 2012 report has no Export menu in Internet Explorer 9.0 if you use the out-of-box viewer page in the SharePoint Report Viewer Web Part (http://support.microsoft.com/kb/2616481).
I’m hoping the Zoom problem and my modal dialog report update problem would be addressed by the fix mentioned in the KB article as well. I’ll update this post eventually when we apply the CU.

UPDATE 7/16/12: I installed SQL Server 2012 CU2 (11.0.2325), which is supposed to include the fixes from CU1 (mentioned in the kb article above), and it has not, unfortunately, fixed any of the issues mentioned in this post.

UPDATE 11/14/12: I installed SQL Server 2012 SP1 (11.0.3000) with the same results: none of the issues mentioned in this post have been addressed.

Wednesday, April 11, 2012

SharePoint Designer 2010 and PerformancePoint Web Part Connections (Part 3)

Part 1, Part 2, Part 3

In the two previous parts to this post, I provided a function to connect two PerformancePoint Services (PPS) web parts and a function to connect a SharePoint Filter to a PPS web part. These functions work great, but only if you know the arguments to pass. These arguments can be GUIDs, for example, in the case of connecting a PPS web part to a Scorecard's row or setting a PPS web part's Display Condition based on a PPS Filter.

In this post, I'll solve that problem for you by providing a function that can create all of the function calls for you for a web part page that has existing connections on it. The purpose of this function is to record the connections on the page so that when they are lost (e.g. by saving a web part page using SharePoint Designer), the script can recreate them in seconds.

The Create-Connection-Script function below loops through all of the connections on a web part page, identifying the type connections and creates a script full of function calls to the Create-BIDataProvider-To-TransformableBIDataProvider-Connection and Create-IFilterValues-To-ITransformableFilterValues-Connection functions. If you create the connection script prior to editing and saving a web part page in SharePoint Designer, you'll be able to run the newly created script afterwards and your PPS web parts will be reconnected.

An example of how to call the Create-Connection-Script function for a web part page with exisitng connections:

# Script that contains functions
. M:\ps1\connections\ConnectWebParts.ps1

clear-host

try
{
    $scriptFolder = "M:\ps1\connections"

    if (!(Test-Path -path $scriptFolder))
    {
        New-Item $scriptFolder -type directory
    }

    $date = Get-Date -format "yyMMddHHmmss"

    $web = Get-SPWeb "http://servername/sitecollectionname/webname"
    $wpm = $web.GetLimitedWebPartManager($web.Url + "DashboardPages/Demo.aspx", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)
    $scriptPath = Join-Path $scriptFolder -childpath "$($date)DemoConnections.ps1"    
    Create-Connection-Script $wpm $scriptPath
}
catch [Exception]
{
    write-host $_.Exception.ToString() -ForegroundColor Red
}
finally
{
    $wpm.Dispose() 
    $web.Dispose()
}   
 
The function in it's entirety (place this in a .PS1 file along with the functions provided in Parts 1 and 2):

function Create-Connection-Script {
    param(
        [Parameter(mandatory=$true)] $wpm,
        [Parameter(mandatory=$true)] [string]$scriptPath
    )

    try
    {
        $m_scriptPath = $scriptPath
        clear-script
        
        $wpConnections = $wpm.SPWebPartConnections
        write-script "# This script was generated by $($MyInvocation.MyCommand)" -Color Green
        write-script "# Script that contains functions"
        write-script ". M:\ps1\connections\ConnectWebParts.ps1"
        write-script "# Script for $($wpConnections.Count) connection(s) on web part page ($($wpm.ServerRelativeUrl))." -Color Green
        write-script ""
        write-script "try"
        write-script "{"
        write-script "    `$web = `Get-SPWeb `"$($web.Url)`""
        write-script "    `$wpm = `$web.GetLimitedWebPartManager(`"$($web.Url.Replace($web.ServerRelativeUrl, $wpm.ServerRelativeUrl))`", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)"
        write-script ""
        
        write-host $web.Url.Replace($web.ServerRelativeUrl, $wpm.ServerRelativeUrl)
        
        foreach ($wpc in $wpConnections)
        {
            write-script "    # $($wpc.Consumer.Title) To $($wpc.Provider.Title)" -Color Green

            if($wpc.ConsumerConnectionPointID -eq "BIDataProvider" -and $wpc.ProviderConnectionPointID -eq "TransformableBIDataProvider")
            {
                write-script "    `$params = @{"
                write-script "        wpm = `$wpm"
                write-script "        wpConsumerTitle =`"$($wpc.Consumer.Title)`""
                write-script "        wpProviderTitle =`"$($wpc.Provider.Title)`""
                
                foreach($record in $wpc.Transformer.ConfigurationState.ProviderConsumerTransformations.Records)
                {
                    write-script "        consumerParameterName = `"$($record.ConsumerParameterName)`""
                    write-script "        providerParameterDisplayName = `"$($record.ProviderParameterDisplayName)`""
                    write-script "        providerParameterName = `"$($record.ProviderParameterName)`""
                    write-script "        providerFormula = `"$($record.ProviderFormula)`""
                }

                $cvr = $wpc.Transformer.ConfigurationState.ConditionalVisibilityRecord         

                if($cvr.IsDefined) 
                {
                    write-script "        cvrIsDefined = `$True"
                    write-script "        cvrProviderParameterDisplayName = `"$($cvr.ProviderParameterDisplayName)`""
                    write-script "        cvrProviderParameterName = `"$($cvr.ProviderParameterName)`""

                    $cvrVisibilitySelections = ""
                    foreach($vs in $cvr.VisibilitySelections)
                    {
                        if($cvrVisibilitySelections.length -ne 0)
                        {
                            $cvrVisibilitySelections += ","
                        }
                        $cvrVisibilitySelections += "`"$vs`""
                    }
                    write-script "        cvrVisibilitySelections = @($($cvrVisibilitySelections))"
                    write-script "        cvrIsDefaultVisibility = $([System.Convert]::ToInt32($cvr.IsDefaultVisibility))"
                }                
                write-script "        reconnect = `$False"
                write-script "    }"
                write-script "    Create-BIDataProvider-To-TransformableBIDataProvider-Connection @params"
            }
            elseif($wpc.ConsumerConnectionPointID -eq "IFilterValues" -and $wpc.ProviderConnectionPointID -eq "ITransformableFilterValues")
            {
                write-script "    `$params = @{"
                write-script "        wpm = `$wpm"
                write-script "        wpConsumerTitle =`"$($wpc.Consumer.Title)`""
                write-script "        wpProviderTitle =`"$($wpc.Provider.Title)`""
                write-script "        mappedConsumerParameterName =`"$($wpc.Transformer.MappedConsumerParameterName)`""
                write-script "        reconnect = `$False"
                write-script "    }"
                write-script "    Create-IFilterValues-To-ITransformableFilterValues-Connection @params"
            }
            else
            {
                write-host "    # Web parts did not have a supported connection" -ForegroundColor Green
            }
            write-script ""
        }
        
        write-script "}"
        write-script "catch [Exception]"
        write-script "{"
        write-script "    `write-host `$_.Exception.ToString() -ForegroundColor Red"
        write-script "}"
        write-script "finally"
        write-script "{"
        write-script "    `$wpm.Dispose()"
        write-script "    `$web.Dispose()"
        write-script "}"
    }
    catch [Exception]
    {
        write-host $_.Exception.ToString() -ForegroundColor Red
    }    
}

function Write-Script {
    param(
        [Parameter(mandatory=$false)] [string]$text,
        [System.ConsoleColor] $color="Gray"
    )
    
    #$text = "$(Get-Date -format g): $text"
    
    write-host $text -ForegroundColor $color
    
    out-file $m_scriptPath -Append -InputObject $text
}
 
Additionally, you'll note the write-script function at the bottom, which I use to write to a file and to the screen.

Hopefully, you can use these functions the way that I have been using them for the past several months, or you can use them to create your own functions to make custom dashboard pages easier to maintain.

Thursday, April 5, 2012

SharePoint Designer 2010 and PerformancePoint Web Part Connections (Part 2)

Part 1, Part 2, Part 3

In Part 1 of this post, I provided a function that can reconnect PerformancePoint Services (PPS) web parts as an alternative to having to reconnect the web parts via the browser. This can come in handy when you've lost all of your PPS connections after having saved your web part page in SharePoint Designer.

In this post, I'm providing a function, Create-IFilterValues-To-ITransformableFilterValues-Connection, that can connect a SharePoint Filter to PPS web parts. I had to create this function to connect Query String (URL) Filters (QSUF) to my PPS web parts. Now this wasn't totally necessary, as these connections are not destroyed by saving your page in SharePoint Designer, but I have used it in various scenarios (e.g. deployment, creating new pages from existing) and it is referenced in the function that I'll provide in Part 3.

An example of connecting a QSUF to a PPS SSRS report:

try
{
    $web = Get-SPWeb "http://servername/sitecollectionname/webname"
    $wpm = $web.GetLimitedWebPartManager("http://servername/sitecollectionname/webname/DashboardPages/demo.aspx", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)

    # Report: Share by Market Scatter Plot To Area Filter
    $params = @{
        wpm = $wpm
        wpConsumerTitle ="Report: Share by Market Scatter Plot"
        wpProviderTitle ="Area Filter"
        mappedConsumerParameterName ="SqlReportViewUniqueParameterIdSI4"
        reconnect = $False
    }
    Create-IFilterValues-To-ITransformableFilterValues-Connection @params
}
catch [Exception]
{
    write-host $_.Exception.ToString() -ForegroundColor Red
}
finally
{
    $wpm.Dispose()
    $web.Dispose()
}
Here is the function in its entirety:

function Create-IFilterValues-To-ITransformableFilterValues-Connection {
    param(
        [Parameter(mandatory=$true)] $wpm,
        [Parameter(mandatory=$true)] [string]$wpConsumerTitle,
        [Parameter(mandatory=$true)] [string]$wpProviderTitle,
        [Parameter(mandatory=$true)] [string]$mappedConsumerParameterName,
        [bool]$reconnect=$false
    )
    
     try
    {
        $foundConnection = $false
        
        $wpConnections = $wpm.SPWebPartConnections

        # find connection
        foreach ($wpc in $wpConnections)
        {
            if ($wpc.Consumer.Title -eq $wpConsumerTitle -and $wpc.Provider.Title -eq $wpProviderTitle)
            {
                if ($reconnect -ne $true) {
                    $foundConnection = $true
                }
                else {
                    $wpm.SPDisconnectWebParts($wpc)
                }
                break
            }
        }

        if($foundConnection -eq $false)
        {
            # get consumer and provider web parts
            $wpConsumer = $wpm.WebParts | Where {$_.Title -eq "$wpConsumerTitle"}
            $wpProvider = $wpm.WebParts | Where {$_.Title -eq "$wpProviderTitle"}
 
            # get consumer and provider connection points
             $consumerConnectionPoint = $wpm.GetConsumerConnectionPoints($wpConsumer)["IFilterValues"]
             $providerConnectionPoint = $wpm.GetProviderConnectionPoints($wpProvider)["ITransformableFilterValues"]

             if($consumerConnectionPoint -ne $null -and $providerConnectionPoint -ne $null)
             {
                 $transformer = New-Object Microsoft.SharePoint.WebPartPages.TransformableFilterValuesToFilterValuesTransformer
                 $transformer.MappedConsumerParameterName = $mappedConsumerParameterName

                 # connect the web parts
                 $wpConnection = $wpm.SPConnectWebParts($wpProvider, $providerConnectionPoint, $wpConsumer, $consumerConnectionPoint, $transformer) 

                 write-host "Successfully connected: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor Green
             }
             else
             {
                 write-host "Web parts did not have supported ITransformableFilterValues-To-IFilterValues connection types: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor Green
             }
        }
        else
        {
             write-host "Connection already exists: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor DarkCyan
        }
    }
    catch [Exception]
    {
        write-host $_.Exception.ToString() -ForegroundColor Green
    }
    finally
    {
    }    
}
In Part 3 of this post, I'll provide a function that uses the functions from Part 1 and this part to create a connection script for you when you provide it a web part page full of connections.

Thursday, March 29, 2012

SharePoint Designer 2010 and PerformancePoint Web Part Connections (Part 1)

Part 1, Part 2, Part 3

When using PerformancePoint Services (PPS) web parts in a web part page to create custom dashboards, you’ll typically set up connections between the (PPS, OOTB and custom) web parts using your web browser. Once your connections are established, you’ll realize that you don’t want to run through the process more than once, especially if you have tens of connections on the page. For a project that I am currently working on, I have several dashboards consisting of around 80 connections each (thankfully, I didn't have to create all of the connections at once).

What I quickly discovered (as many have) is that if you have to use SharePoint Designer to customize the web part page after the PPS connections have been established, you'll run into a very painful problem. It turns out that upon saving a web part page with PPS connections in SharePoint Designer, all of the PPS connections are lost.

My solution, after a lot of Googling and trial and error, has been to create PowerShell functions that connect PPS web parts (Part 1), connect PPS web parts to SharePoint filters (Part 2), and create connection scripts by having PowerShell examine the connections on a web part page (Part 3).

The Create-BIDataProvider-To-TransformableBIDataProvider-Connection function, provided below, will handle the following connections:
  • Scorecard to PPS Filter
  • Analytic Report (i.e. Chart or Grid) to PPS Filter
  • Analytic Report to Scorecard
  • PPS SSRS Report to PPS Filter
  • PPS SSRS Report to Scorecard
It will also handle Connection Formulas and Display Conditions. There's certainly room for improvement, but it handles everything that I need it to do regarding PPS web part connections.

An example of calling the function to connect a Scorecard to a PPS Filter:

try
{   
    $web = Get-SPWeb "http://servername/sitecollectionname/webname"
    $wpm = $web.GetLimitedWebPartManager("http://servername/sitecollectionname/webname/DashboardPages/demo.aspx", [System.Web.UI.WebControls.WebParts.PersonalizationScope]::Shared)

    $params = @{
        wpm = $wpm
        wpConsumerTitle ="Scorecard"
        wpProviderTitle ="Area Filter"
        consumerParameterName = "EndPoint_Page"
        providerParameterDisplayName = "PerformancePoint Values"
        providerParameterName = "FilterValues"
        providerFormula = ""
        reconnect = $False
    }
    Create-BIDataProvider-To-TransformableBIDataProvider-Connection @params
}
catch [Exception]
{
    write-host $_.Exception.ToString() -ForegroundColor Red
}
finally
{
    $wpm.Dispose()
    $web.Dispose()
}

I won't bore you with a bunch of examples, because in Part 3, I'll provide a script that will take your web part page and create all of the function calls for you.

Below is the function is in entirety:

# cvr parameters: ConditionalVisibilityRecord Properties
function Create-BIDataProvider-To-TransformableBIDataProvider-Connection {
    param(
        [Parameter(mandatory=$true)] $wpm,
        [Parameter(mandatory=$true)] [string]$wpConsumerTitle,
        [Parameter(mandatory=$true)] [string]$wpProviderTitle,
        [string]$consumerParameterName="",
        [string]$providerParameterDisplayName="",
        [string]$providerParameterName="",
        [string]$providerFormula="",
        [bool]$cvrIsDefined=$false,
        [string]$cvrProviderParameterDisplayName="",
        [string]$cvrProviderParameterName="",
        [string[]]$cvrVisibilitySelections = @(),
        [bool]$cvrIsDefaultVisibility=$false,
        [bool]$reconnect=$false
    )
    
     try
    {
        $foundConnection = $false
        
        $wpConnections = $wpm.SPWebPartConnections

        # find connection based on consumer and provider web part titles 
        foreach ($wpc in $wpConnections)
        {
            if ($wpc.Consumer.Title -eq $wpConsumerTitle -and $wpc.Provider.Title -eq $wpProviderTitle)
            {
                if ($reconnect -ne $true) {
                    $foundConnection = $true
                }
                else {
                    $wpm.SPDisconnectWebParts($wpc)
                }
                break
            }
        }

        # if found, attempt the connection process
        if($foundConnection -eq $false)
        {
            # get the consumer and provider web parts
            $wpConsumer = $wpm.WebParts | Where {$_.Title -eq "$wpConsumerTitle"}
            $wpProvider = $wpm.WebParts | Where {$_.Title -eq "$wpProviderTitle"}
           
            # get the consumer and provider connection points
            $consumerConnectionPoint = $wpm.GetConsumerConnectionPoints($wpConsumer)["BIDataProvider"] 
            $providerConnectionPoint = $wpm.GetProviderConnectionPoints($wpProvider)["TransformableBIDataProvider"]
            
            [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.PerformancePoint") 

            if($consumerConnectionPoint -ne $null -and $providerConnectionPoint -ne $null)
            {
                $pcTransformations = New-Object Microsoft.PerformancePoint.Scorecards.ProviderConsumerTransformations

                # note: the consumerParameterName may be empty if a Display Condition only
                if($consumerParameterName.length -ne 0)
                {
                    # establish the connection settings
                    $tpcRecord = New-Object Microsoft.PerformancePoint.Scorecards.TransformProviderConsumerRecord
                    $guid = [guid]::NewGuid()
                    $tpcRecord.MappingId = $guid.ToString()
                    $tpcRecord.ConsumerParameterName = $consumerParameterName
                    $tpcRecord.ProviderParameterDisplayName = $providerParameterDisplayName
                    $tpcRecord.ProviderParameterName = $providerParameterName
                    $tpcRecord.TypeFullName = "System.String"
                    $tpcRecord.ProviderFormula = $providerFormula
                    $tpcRecord.ValuesColumnName = "MemberUniqueName"
                    $tpcRecord.DisplayColumnName = "DisplayValue"
                    $tpcRecord.EncodeAsSet = $false
                    $pcTransformations.Records.Add($tpcRecord)
                }
                
                # conditional visibility
                $tcvRecord = New-Object Microsoft.PerformancePoint.Scorecards.TransformConditionalVisibilityRecord
                $tcvRecord.IsDefined = $cvrIsDefined
                $tcvRecord.ProviderParameterDisplayName = $cvrProviderParameterDisplayName
                $tcvRecord.ProviderParameterName = $cvrProviderParameterName
                foreach($vs in $cvrVisibilitySelections)
                {
                    $tcvRecord.VisibilitySelections.Add($vs)
                }
                $tcvRecord.IsDefaultVisibility = $cvrIsDefaultVisibility

                $tcRecord = New-Object Microsoft.PerformancePoint.Scorecards.TransformerConfigurationRecord($pcTransformations, $tcvRecord)
                
                $transformer = New-Object Microsoft.PerformancePoint.Scorecards.WebControls.TransformableBIDataProviderTransformer
                $guid = [guid]::NewGuid()
                $transformer.ID = $guid.ToString()
                $transformer.ConfigurationState = $tcRecord

                # finally create the connection
                $wpConnection = $wpm.SPConnectWebParts($wpProvider, $providerConnectionPoint, $wpConsumer, $consumerConnectionPoint, $transformer) 
                #$wpConnections.Add($wpConnection)

                 write-host "Successfully connected: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor Green
             }
             else
             {
                 write-host "Web parts did not have supported BIDataProvider-To-TransformableBIDataProvider connection types: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor Green
             }
        }
        else
        {
             write-host "Connection already exists: $wpConsumerTitle, $wpProviderTitle" -ForegroundColor DarkCyan
        }
    }
    catch [Exception]
    {
        write-host $_.Exception.ToString() -ForegroundColor Green
    }
    finally
    {
    }    
}
 
In the part of this post, I'll provide a function that connects SharePoint Filters to PPS web parts.

Saturday, December 17, 2011

Using the DotNetZip Library with Pervasive EZScript

We are often required to work with compressed files in our Pervasive Data Integrator processes. We receive files that are compressed, we use file compression for archiving and we provide files to our clients that we compress prior to transmitting them.

The DotNetZip Library is great free file-compression library that you can use with your own applications or script. Used with Pervasive's EZScript, you can add zipping and unzipping functionality to any Data Integrator process.

To use the library I had to first install .Net Framework 3.5.1 Feature on our Windows Server 2008 R2 that hosts Pervasive Data Integrator. Next, I downloaded the DotNetZipUtils-v1.9.msi to install the runtime DLL. Finally, I used this excellent PowerShell script written by "Fred" that I lifted from here to install the Ionic.Zip.dll into the Global Assembly Cache.

I've included a couple of functions below to get you started with using file compression in EZScript.

ZIP function:

Function ZipFile(fileName, zipFileName, password)
 Dim oZipFile As Object
 Dim fileNames(), i

 ReDim fileNames(UBound(Split(fileName, ";")))
 fileNames = Split(fileName, ";")

 Set oZipFile = CreateObject("Ionic.Zip.ZipFile")
 '"using AES256 encryption...") 
 'oZipFile.Encryption = 3  

 ' same password all items
 If Len(password) <> 0 Then
  oZipFile.Password = password
 End If

 For i = 0 To UBound(fileNames)
  'oZipFile.AddItem(fileName)  
  oZipFile.AddItem_2(Trim(fileNames(i)), "") 
 Next

 oZipFile.Name = zipFileName
 LogMessage("INFO", "Zipping file: " & zipFilename)
 oZipFile.Save()
 LogMessage("INFO", "File zipped: " & zipFilename)

 oZipFile.Dispose()
 Set oZipFile = Nothing

End Function

Unzip function:

' ExtractExistingFileAction: 0= Throw; 1= OverwriteSilently; 2=DoNotOverwrite 
Function UnZipFile(zipFileName, password, unZipDirectory, ExtractExistingFileAction)
 Dim oZipFile As Object
 
 Set oZipFile = CreateObject("Ionic.Zip.ZipFile")
 '"using AES256 encryption...") 
 'oZipFile.Encryption = 3  

 oZipFile.Initialize(zipFileName)

 ' same password all items
 If Len(password) <> 0 Then
  oZipFile.Password = password
 End If

 oZipFile.ExtractAll_2(unZipDirectory, ExtractExistingFileAction)

 LogMessage("INFO", "Files extracted to " & unZipDirectory)

 oZipFile.Dispose()
 Set oZipFile = Nothing

End Function

And an example of how to call the functions:

Option Explicit

Dim zipFileName, password

' an example of zipping multiple files
ZipFile("\\fileshare\myFolder\myAccessFile.mdb;\\fileshare\myFolder\myTextFile.txt", _
 "\\fileshare\myFolder\myFile.zip", "Super Secret Password")

Dim unZipDirectory, overwriteAction
zipFileName = "\\fileshare\myFolder\myZippedFile.zip"
password = ""
unZipDirectory = "\\fileshare\myFolder\tmpZIP\"
' overwriteAction: 0=throw error; 1=overwrite silently; 2=do not overwrite
overwriteAction = 1

UnZipFile(zipFileName, password, unZipDirectory, overwriteAction)

Hopefully these examples can help you get started with an easy-to-use file compression (and decompression) in your Pervasive Data Integrator processes.