Using Microsoft Report Viewer In PowerShell

In this post I’m going to give a quick example using Microsoft Report Viewer in PowerShell. PowerShell makes is easy enough to slap together a simple report by converting data to HTML and adding some CSS but every now and then you need something a bit more polished. For instance generating a chart and or report in Word format that is ready for distribution or printing, laid out according to page size and orientation with headers, footers, logos etc. HTML doesn’t work that great for this so I did a POC using Microsoft ReportViewer 2012, this should work with the 2015 version as well but I didn’t try it.

I’m not going to dig into creating reports with the report viewer in this post, I’ll be focusing on using it with PowerShell. If you are not familiar with the report viewer you can catchup over here and there are some very good resources and samples on the GotReportViewer website as well. Short version, you design reports using the report designer in Visual Studio, at run time you pass it data and render the report to a file or display it in the report viewer. If you don’t have the report viewer installed you can download it here. The whole solution is in my GitHub repo here but I will be highlighting/explaining some aspects of it as we go along.

Code Highlights
When you design the report you have to assign data sources to it that will provide the report with the fields it will use at design time to author the report and it will also expect the same objects at run time with the populated data. You’ll have to create these in a .NET project, compile it and load the assembly along with the ReportViewer assembly into PowerShell.

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.ReportViewer`.WinForms") 
[System.Reflection.Assembly]::LoadWithPartialName("ReportPOC")

In this solution the data source is the ReportData class in the ReportPOC assembly. When you create the data source collection you have to create a strongly typed collection, in this case a generic List of the data source type.

$data = New-Object "System.Collections.Generic.List[ReportPOC.ReportData]"

Creating the records and adding them to the data source is pretty straight forward.

$item1 = New-Object ReportPOC.ReportData
$item1.ServerName = "Server1"
$item1.CPUAvail = "128"
$item1.CPUUsed = "103"
$data.Add($item1)

Then you create the report viewer instance specify the path to your report file and add the data source.

$rep = New-Object Microsoft.Reporting.WinForms.ReportDataSource
$rep.Name = "ReportDS"
$rep.Value = $data
$rv.LocalReport.ReportPath = "C:\MySource\ReportPOC\POC.rdlc";
$rv.LocalReport.DataSources.Add($rep);

Next you render the report and write the output byte array to a file stream, remember to cast the render result to type [byte[]] or else it won’t work.

[byte[]]$bytes = $rv.LocalReport.Render("WORDOPENXML");
$fs = [System.IO.File]::Create("C:\MySource\ReportPOC\POC.docx")
$fs.Write( [byte[]]$bytes, 0, $bytes.Length);
$fs.Flush()
$fs.Close()

The result, a chart and report using the same data source with a page break between them as it displays in Word 2013:

Using Microsoft Report Viewer In PowerShell

Next Steps
If you want a re-usable solution I would create a more generic data source class to avoid coding a new data source class for different charts. Also add some parameters to the report/chart to control the headings, legend and labels, page headers/footers etc. You can also export to other formats by passing different parameters to the render method like “EXCEL”, “PDF”, “HTML” and “XML”. You can also create different series to group categories and apply logic to the report/chart to calculate values or control colors for example if CPU usage is > 90 % color the bar red, this is done in the report designer.

Francois Delport

Out Parameters, Nested Classes, Specifying A Proxy, Modules And Local Variables And Debugging Jobs In PowerShell

In post I’m going to cover out parameters, nested classes, specifying a proxy, modules and local variables and debugging jobs in PowerShell

Calling .NET Functions With Out Parameters
When you call a .NET function that requires an out parameter you have to cast the parameter as a System.Management.Automation.PSReference object by using a [ref] cast. Apart from the cast you also have to initialize the variable, this didn’t seem obvious at first since you don’t have to do it in .NET but in PowerShell you have to.

$parsedip = New-Object System.Net.IPAddress -ArgumentList 1
[System.Net.IPAddress]::TryParse("192.168.10.88", [ref] $parsedip)

Using Nested .NET Classes In PowerShell
Usually you don’t come across nested classes very often but when you do they have different syntax from other classes. To access a nested class use the + sign before the name of the nested class instead of a full stop.

Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo

Specifying A Proxy At Script Level
If you want to specify a proxy that will be used for web requests but you can’t or don’t want to change the proxy settings machine wide you can specify it in your script.

$proxyString = "http://someserver:8080"
$proxyUri = new-object System.Uri($proxyString)
[System.Net.WebRequest]::DefaultWebProxy = new-object System.Net.WebProxy ($proxyUri, $true)

Modules And Local Variables
Variables declared local to a module will not hold their value between function calls to the module. For example if you create a counter that is incremented every time you call a function in the module that variable will be created as a new local  variable every time the function begins execution. To reference the same variable between function calls you have to declare it at script scope. This will not clash with variables declared in scripts that use the module if they also have a variable with the same name declared in script scope.

$script:localvar = 0

function DoOne
{
 write-output "DoOne : $script:localvar"
 $script:localvar += 1
}

Debugging Jobs
Since jobs start their own Powershell.exe process when you run them you can’t step into the code from your IDE even if you have a breakpoint set in the script. You have to use the Debug-Job cmdlet in your script running the job to start a debug session to the remote process. It will start debugging at the currently executing line in the job. If you want the job to wait for you at a specific line so you can start debugging at that point use the Wait-Debugger cmdlet in the script being executed by the job.

Francois Delport

PowerShell Active Directory Interaction Without Using Active Directory Modules

I recently had the requirement to interact with Active Directory using PowerShell but without using the *AD* cmdlets from the ActiveDirectory PowerShell module. The reason being the script would be running from a server that won’t meet the requirements to install the module and it was running Powershell V2. In this post I’ll be looking at two alternatives to achieve PowerShell Active Directory interaction without using Active Directory modules. Just to reiterate the easiest way to interact with Active Directory from PowerShell is the Active Directory module, link here. But if you can’t these alternatives will come in handy.

 

Find an object example:

$domain = New-Object DirectoryServices.DirectoryEntry
 $search = [System.DirectoryServices.DirectorySearcher]$domain
 $search.Filter = "(&(objectClass=user)(sAMAccountname=UserToLookFor))"
 $user = $search.FindOne().GetDirectoryEntry()

When it comes to creating objects you can create generic Directory entries and populate their Active Directory attributes directly but that would require knowledge of the attributes before hand.

$objDomain = New-Object System.DirectoryServices.DirectoryEntry 
$obj = $objDomain.Create("$name""CN=ContainerName"$obj.Put("AttributeName","AttributeValue")
$obj.CommitChanges()

Or if you are on .Net 3.5 or higher you can use classes from the System.DirectoryServices.AccountManagement namespace to create typed objects.

Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctx = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$obj = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $ctx
$obj.Name = "test"
$obj.Save()
  • Using Active Directory Service Interface ADSI. I mostly used the Exists static method on the ADSI class to check if objects exist in Active Directory.
    [adsi]::Exists("LDAP://OU=test,DC=domain,DC=com")

    You can also use the ADSI PowerShell adapter to manipulate objects, there are a few examples here on TechNet. I found the classes from the ActiveDirectory namespace easier to use when it comes to manipulating objects but the Exists method works well if you already have the Distinguished Name of an object.

Francois Delport