I use Powershell to run several reports on Microsoft SQL Report Services and to save the results to a Word doc. I have a script with functions that handle communications with the Report Server:
## File "qrap-functions.ps1"
function GetRSConnection($server, $instance)
{
$User = "xxxx"
$PWord = ConvertTo-SecureString -String "yyyy" -AsPlainText -Force
$c = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$reportServerURI = "http://" + $server + "/" + $instance + "/ReportExecution2005.asmx?WSDL"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -Credential $c
$RS.Url = $reportServerURI
return $RS
}
function GetReport($RS, $reportPath)
{
$reportPath = "/" + $reportPath
#$reportPath
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
$parameters = #()
$RS.SetExecutionParameters($parameters, "nl-nl") > $null
return $report
}
function AddParameter($params, $name, $val)
{
$par = New-Object RS.ParameterValue
$par.Name = $name
$par.Value = $val
$params += $par
return ,$params
}
function GetReportInFormat($RS, $report, $params, $format, $saveas)
{
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
$RS.SetExecutionParameters($params, "nl-nl") > $null
$RenderOutput = $RS.Render($format,
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
$Stream = New-Object System.IO.FileStream($saveas), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
}
Then, I have a script that executes a report containing the financial quarterly data. This script runs fine:
## File "qrap-financieel.ps1"
. "./qrap-functions.ps1"
$saveas = "e:\test\financieel.doc"
$RS = GetRSConnection -server "MSZRDWH" -instance "reportserver_acc"
$report = GetReport -RS $RS -reportPath "kwartaalrapportage/kwartaalrapportage financieel"
$params = #()
$kwartaal = "[Periode Maand].[Jaar Kwartaal].&[2015-2]"
$kptc = "[Kostenplaats].[Team code].&[2003]"
$params = AddParameter -params $params -name "PeriodeMaandJaarKwartaal" -val $kwartaal
$params = AddParameter -params $params -name "KostenplaatsTeamcode" -val $kptc
GetReportInformat -RS $RS -report $report -params $params -format "WORD" -saveas $saveas
The values for $kwartaal and $kptc are hard-coded here, but are parameters in the actual version of this script. Besides the financial quarterly, we have three other quarterly reports that need to be output by this script.
Two of these run fine, in the fourth I can't seem to get one of the parameters right. The script for that one is:
## File "qrap-zorglog.ps1"
. "./qrap-functions.ps1"
$RS = GetRSConnection -server "MSZRDWH" -instance "reportserver_acc"
$report = GetReport -RS $RS -reportPath "kwartaalrapportage/kwartaalrapportage zorglogistiek"
$s = "Urologie"
$saveas = "e:\test\ZL Urologie.doc"
$params = #()
$kwartaal = "[Periode Maand].[Jaar Kwartaal].&[2015-2]"
$params = AddParameter -params $params -name "HoofdspecialismeSpecialismeOms" -val "[Hoofdspecialisme].[Specialisme Oms].&[$s]"
$params = AddParameter -params $params -name "PeriodeMaandJaarKwartaal" -val $kwartaal
$params = AddParameter -params $params -name "WachttijdenSpecialismeSpecialisme" -val "[Wachttijden Specialisme].[Specialisme].&[$s]"
$params = AddParameter -params $params -name "SpecialisatieGroeperingSpecialisatieGroeperingOms" -val "[Specialistie Groepering].[Specialistie Groepering Oms].&[$s]"
$params = AddParameter -params $params -name "AanvragendSpecialismeSpecialismeOms" -val "[AanvragendSpecialisme].[Specialisme Oms].&[$s]"
GetReportInformat -RS $RS -report $report -params $params -format "WORD" -saveas $saveas
When I execute this script, I get this error:
Exception calling "Render" with "7" argument(s): "System.Web.Services.Protocols.SoapException: This report requires a
default or user-defined value for the report parameter 'HoofdspecialismeSpecialismeOms'. To run or subscribe to this
report, you must provide a parameter value. ---> Microsoft.ReportingServices.Diagnostics.Utilities.ReportParameterValueNot
SetException: This report requires a default or user-defined value for the report parameter
'HoofdspecialismeSpecialismeOms'. To run or subscribe to this report, you must provide a parameter value.
I clearly DO supply a value for 'HoofdspecialismeSpecialismeOms'; I've previously noticed that this error also is thrown when the parameter is not in the expected format. This format, since the
report filter is based on a hierarchy in an SSAS cube, looks like this: [hierarchy].[sub-level].&[member]. I've ensured that [Hoofdspecialisme].[Specialisme Oms].&[$s] is the correct format by
looking it up in the query that populates the prompt in SSRS. The report does display data when run through SSRS - and taking a parameter from the prompt.
I did notice that this parameter allows multiple selection. However, I don't believe this leads to the error because that is also true for AanvragendSpecialismeSpecialismeOms.
Any idea why this one parameter fails to be fed into the report when calling GetReportInformat?
Have you tried
function AddParameter($params, $name, $val)
{
$par = New-Object RS.ParameterValue
$par.Name = $name
$par.Value = $val
$params += $par
return ,$params
# ^Removing this comma?
}
As well as declaring the data types explicitly for your parameters?
function AddParameter([Array]$params, [String]$name, [String]$val)
{
$par = New-Object RS.ParameterValue
$par.Name = $name
$par.Value = $val
$params += $par
return ,$params
}
Also, with so many user-defined helper functions calling imported types that call methods and set properties to a report we can't see, it can get a little difficult to help troubleshoot in-depth for this specific report you're getting an error on. It looks like you've tried moving the line around in the order which sounds to me like you might have an issue with how that specific report parses the values you input through RS.ParameterValue so maybe take a look at if it accepts the string you set in -val for your AddParameter user defined function.
Edit:
From https://social.msdn.microsoft.com/Forums/sqlserver/en-US/e38b4a34-c780-43bb-8321-15f96d0938a9/exception-calling-render-systemwebservicesprotocolssoapexception-one-or-more-data-source?forum=sqlreportingservices
This error is generated when you are attempting to run a report in which one or more of the data sources are set to "prompt" credentials. This means we do not use your Windows credentials automatically, but rather you need to supply a different set of credentials which are used only for the data source.
Sounds like you might need to put aside the script and check if the report is different.
I've finally figured it out: The failing prompt had a multi-select enabled. And when filling in a multi-select, SSRS expects a list of values. When only given one string, the string is ignored and the parameter is assumed blank.
To feed it a list, we must do:
$multival = New-Object System.Collections.Specialized.StringCollection
$multival.Add("[Hoofdspecialisme].[Specialisme Oms].&[$s]")
[snip]
$params = AddParameter -params $params -name "HoofdspecialismeSpecialismeOms" -val $multival
Found the answer thanks to this question:
How to pass multiple value parameter to reporting services report via powershell
Related
I have a PowerShell GUI that is pulling some values from a SSRS report using an String array input. However, as this would freeze the GUI, I decided to use Start-Job to start a job that pulls the SSRS report while a ProgressBar keeps running in the GUI.
The SSRS report has only one input parameter. When I use Start-Job to Render the report using multiple values, I get the result of only the first record irrespective of the number of input values.
The same function works smoothly when called natively without Start-Job returning the records for all the input values.
This is the code:
$GetSSRSData = {
param([string[]]$InputArray)
$reportServerURI = "https://<SERVER>/ReportServer/ReportExecution2005.asmx?wsdl"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -UseDefaultCredential
$RS.Url = $reportServerURI
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
$reportPath = "/Folder/Report"
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
# Report parameters are handled by creating an array of ParameterValue objects.
$parameters = #()
for($i = 0; $i -lt $InputArray.Count; $i++){
$parameters += New-Object RS.ParameterValue
$parameters[$i].Name = "ParameterName"
$parameters[$i].Value = "$($InputArray[$i])"
}
# Add the parameter array to the service. Note that this returns some
# information about the report that is about to be executed.
$RS.SetExecutionParameters($parameters, "en-us") > $null
# Render the report to a byte array. The first argument is the report format.
$RenderOutput = $RS.Render('CSV',
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
$output = [System.Text.Encoding]::ASCII.GetString($RenderOutput)
return $output
}
$InputArray = #('XXXXXX', 'YYYYYY', 'ZZZZZZ', 'ABCDEF')
<#
# The below code works perfectly
$Data = GetSSRSData -InputArray $InputArray
ConvertFrom-Csv -InputObject $Data
#>
$job = Start-Job -ScriptBlock $GetSSRSData -ArgumentList $InputArray
do { [System.Windows.Forms.Application]::DoEvents() } until ($job.State -ne "Running")
$Data = Receive-Job -Job $job
Write-Host $Data # returns only the first record
When I change the bottom part as shown below, I am able to verify that the job ends after the first record is output.
$RenderOutput = $RS.Render('CSV',
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
Write-Output $RenderOutput
}
$InputArray = #('XXXXXX', 'YYYYYY', 'ZZZZZZ', 'ABCDEF')
$job = Start-Job -ScriptBlock $GetSSRSData -ArgumentList $InputArray
do {[System.Text.Encoding]::ASCII.GetString($job.ChildJobs[0].Output)} until ($job.State -ne "Running")
I also tried adding a sleep function of 5 seconds after the Render function, but it did not make any difference.
Please note that repeatedly calling the Start-Job for each input is not an option as each function call is costing a lot of time and hence the report needs to be pulled in a single call.
Why is the Render function behaving differently when started as a job? Is the function ending prematurely before it can render the other records as well?
Is there any other way such as a Runspace or a Start-ThreadJob that can solve this problem?
Ref: https://stackoverflow.com/a/63253699/4137016
Answer is here: ArgumentList parameter in Invoke-Command don't send all array
Probably better answer here: How do I pass an array as a parameter to another script?
Change -ArgumentList $InputArray to -ArgumentList (,$InputArray)
$InputArray = #('XXXXXX', 'YYYYYY', 'ZZZZZZ', 'ABCDEF')
$job = Start-Job -ScriptBlock $GetSSRSData -ArgumentList (,$InputArray)
I am working with PowerShell 4.0 and I am trying to pass a string array as one of the parameters for an Invoke-Command -ScriptBlock in which I am calling another PowerShell script on a remote server. When I do this, the string array seems to get flattened so that it appears as a single string value, rather than a string array.
Listed below is the 1st script, which is being called by a Bamboo deployment server that provides the initial parameters.
In the Debug section, the $SupportFolders string array is iterated by the FlowerBoxArrayText function and it properly writes the two folder paths to the console, as expected.
24-Oct-2017 14:59:33 *****************************************************************************
24-Oct-2017 14:59:33 **** E:\SRSFiles\SRSOutput
24-Oct-2017 14:59:33 **** E:\SRSFiles\SRSBad
24-Oct-2017 14:59:33 *****************************************************************************
Here is the initial part of the 1st script file, showing the input parameters, the string array creation and where I am calling the remote script via Invoke-Command;
[CmdletBinding(DefaultParametersetName='None')]
param (
# Allows you to specify Install, Delete or Check.
[ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
# Allows you to specify the remote server name.
[string] $ComputerName = "None",
# Allows you to specify the username to use for installing the service.
[string] $Username = "None",
# Allows you to specify the password to use for installing the service.
[string] $Password = "None",
# Allows you to specify the location of the support folders for the service, if used.
[string] $SupportFoldersRoot = "None"
)
Function CreateCredential()
{
$Pass = $Password | ConvertTo-SecureString -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential($Username, $Pass)
Return $Cred
}
Function FlowerBoxArrayText($TextArray, $TextColor="Yellow")
{
Write-Host "*****************************************************************************" -ForegroundColor $TextColor
foreach($TextLine in $TextArray)
{
IndentedText $TextLine $TextColor
}
Write-Host "*****************************************************************************" -ForegroundColor $TextColor
}
Function IndentedText($TextToInsert, $TextColor="Yellow")
{
Write-Host "**** $TextToInsert" -ForegroundColor $TextColor
}
$Credential = CreateCredential
[string[]] $ResultMessage = #()
[string] $Root = $SupportFoldersRoot.TrimEnd("/", "\")
[string[]] $SupportFolders = #("$Root\SRSOutput", "$Root\SRSBad")
#Debug
Write-Host "**** Starting debug in ManageAutoSignatureProcessorService ****"
FlowerBoxArrayText $SupportFolders -TextColor "Green"
Write-Host "**** Ending debug in ManageAutoSignatureProcessorService ****"
#End Debug
$ResultMessage = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
param($_action,$_username,$_password,$_supportFolders) &"C:\Services\ManageService.ps1" `
-Action $_action `
-ComputerName DEV `
-Name DevProcessor `
-DisplayName 'DevProcessor' `
-Description 'DevProcessor' `
-BinaryPathName C:\Services\DevProcessor.exe `
-StartupType Manual `
-Username $_username `
-Password $_password `
-ServicePathName C:\Services `
-SupportFolders $_supportFolders `
-NonInteractive } -ArgumentList $Action,$Username,$Password,(,$SupportFolders)
if ($ResultMessage -like '*[ERROR]*')
{
FlowerBoxArrayText $ResultMessage -textColor "Red"
}
else
{
FlowerBoxArrayText $ResultMessage -textColor "Green"
}
Then, in the ManageService.ps1 script file on the remote server, I have the following;
[CmdletBinding(DefaultParametersetName='None')]
param (
# Allows you to specify Install, Delete or Check.
[ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
# Allows you to specify the name of the remote computer.
[string] $ComputerName = "None",
# Allows you to specify the service name.
[string] $Name = "None",
# Allows you to specify the service display name.
[string] $DisplayName = "None",
# Allows you to specify the service description.
[string] $Description = "None",
# Allows you to specify the path to the binary service executable file.
[string] $BinaryPathName = "None",
# Allows you to specify how the service will start, either manual or automatic.
[ValidateSet("Manual", "Automatic")][string] $StartupType = "Manual",
# Allows you to specify the domain username that the service will run under.
[string] $Username = "None",
# Allows you to specify the password for the domain username that the service will run under.
[string] $Password = "None",
# Allows you to specify the path to the service install scripts and service files on the remote server.
[string] $ServicePathName = "None",
# Allows you to specify the location of the support folders for the service, if used. The default value is an empty array
[string[]] $SupportFolders = #(),
# Disables human interaction, and allows all tests to be run even if they 'fail'.
[switch] $NonInteractive
)
Function CreateCredential()
{
$Pass = $Password | ConvertTo-SecureString -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential($Username, $Pass)
Return $Cred
}
[bool] $OkToInstall = $False
[string[]] $ResultMessage = #()
#Debug
$ResultMessage = $ResultMessage += "[DEBUG] ***************************************"
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders: [$SupportFolders] ."
foreach ($Folder in $SupportFolders)
{
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders Item: $Folder."
}
$Count = #($SupportFolders).Count
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders Count: $Count ."
$ResultMessage = $ResultMessage += "[DEBUG] ***************************************"
#End Debug
The line,
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders: [$SupportFolders] ."
shows the following result from the $ResultMessage value that is returned to the calling script;
**** [DEBUG] SupportFolders: [E:\SRSFiles\SRSOutput E:\SRSFiles\SRSBad] .
Notice that the array is flattened out.
The foreach loop that follows also only prints out one value instead of two;
"E:\SRSFiles\SRSOutput E:\SRSFiles\SRSBad"
I have spent considerable time researching a solution but have yet to find an answer.
Any ideas?
EDIT 1 using #Bacon Bits suggestion;
$Options = #{'Action' = $Action
'ComputerName' = 'DEV'
'Name' = 'DevProcessor'
'DisplayName' = 'DevProcessor'
'Description' = 'Generate daily processes'
'BinaryPathName' = 'C:\Services\DevProcessor\DevProcessor.exe'
'StartupType' = 'Manual'
'Username' = $Username
'Password' = $Password
'ServicePathName' = 'C:\Services\DevProcessor'
'SupportFolders' = $SupportFolders
}
$ScriptBlock = {
param($Options)
& {
param(
$Action,
$ComputerName,
$Name,
$DisplayName,
$Description,
$BinaryPathName,
$StartupType,
$Username,
$Password,
$ServicePathName,
$SupportFolders,
$NonInteractive
)
&powershell "C:\Services\DevProcessor\ManageService.ps1 $Action $ComputerName $Name $DisplayName $Description $BinaryPathName $StartupType $Username $Password $ServicePathName $SupportFolders"
} #Options;
}
$ResultMessage = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock $ScriptBlock -ArgumentList $Options
If I run the code modified as it is listed above, I still get the flattened array for $SuppportFolders and the ManageService.ps1 script trips up over parameters that have spaces, even though they are quoted when I assign them.
The option to completely wrap the code in ManageService.ps1, as opposed to simply calling the remote script is not really viable because the ManagedService.ps1 script is fairly extensive and generic so I can call it from over 30 automation scripts in my deployment server.
I believe what #Bacon Bits is suggesting would work if it was feasible to wrap the ManageService script.
To pass a single array, you can do this:
Invoke-Command -Session $Session -ScriptBlock $ScriptBlock -ArgumentList (,$Array);
However, that only works if you only need to pass a single array. It can all fall apart as soon as you start to pass multiple arrays or multiple complex objects.
Sometimes, this will work:
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList (, $Array1), (, $Array2), (, $Array3);
However, it can be inconsistent in my experience. Sometimes it flattens the arrays out again.
What you can do is something similar to this answer.
{param($Options)& <# Original script block (including {} braces)#> #options }
Basically what we do is:
Wrap the script in a scriptblock that accepts a single hashtable as an argument.
Put all our arguments into the hashtable.
Use the passed hashtable as a splat variable.
So it would be something like:
$Options = #{
Action = 'Check';
ComputerName = 'XYZ123456';
Name = 'MyName';
.
.
.
}
$ScriptBlock = {
param($Options)
& {
[CmdletBinding(DefaultParametersetName='None')]
param (
# Allows you to specify Install, Delete or Check.
[ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
# Allows you to specify the name of the remote computer.
[string] $ComputerName = "None",
# Allows you to specify the service name.
[string] $Name = "None",
.
.
.
.
#End Debug
} #Options;
}
Invoke-Command -ComputerName RemoteServer -ScriptBlock $ScriptBlock -ArgumentList $Options;
Here's a trivial working example:
$Options = #{
List1 = 'Ed', 'Frank';
List2 = 5;
List3 = 'Alice', 'Bob', 'Cathy', 'David'
}
$ScriptBlock = {
param($Options)
& {
param(
$List1,
$List2,
$List3
)
"List1"
$List1
''
"List2"
$List2
''
"List3"
$List3
} #Options;
}
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $Options;
Output:
List1
Ed
Frank
List2
5
List3
Alice
Bob
Cathy
David
Note that I tested this on PowerShell v5. I no longer have a system with PowerShell v4 to test on.
I'm trying to make PowerShell send a web request to our SSRS server and capture the results. I've hit a wall using the rs:FORMAT=EXCEL parameter in the SSRS url string. I have the following:
First, init the credentials:
$User = "MYDOMAIN\MyUser"
$PWord = ConvertTo-SecureString -String "WooHooStringP$W0rd" -AsPlainText -Force
$c = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
Now, request a report:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name"
This works fine. I can even grab the results (enclosing this request and using ().Content).
Then, specify a format instead of plain rendering:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name&rs:format=HTML4.0"
Note the rs:Format specification? Works like a charm.
Then, for the grande finale, give me an Excel file:
Invoke-WebRequest `
-UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) `
-Credential $c `
-Uri "http://myserver/ReportServer_DEV/Pages/ReportViewer.aspx?/folder+path/report+name&rs:format=EXCEL"
No can do, bud:
Invoke-WebRequest : The remote server returned an error: (401) Unauthorized.
At line:1 char:11
+ $bytez = (Invoke-WebRequest `
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Why does the rs:format=EXCEL option throw an Unauthorised exception where all the other URLs are served by SSRS?
I've figured it out! I went about this the wrong way: SSRS offers access through a webservice that PowerShell can consume without the need to hack the URL and capture a response. I found a script that did this and modified it to suit my purpose:
function GetRSConnection($server, $instance)
{
# Create a proxy to the SSRS server and give it the namespace of 'RS' to use for
# instantiating objects later. This class will also be used to create a report
# object.
$User = "DOMAIN\Username"
$PWord = ConvertTo-SecureString -String "Pa$$w0rd" -AsPlainText -Force
$c = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User, $PWord
$reportServerURI = "http://" + $server + "/" + $instance + "/ReportExecution2005.asmx?WSDL"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -Credential $c
$RS.Url = $reportServerURI
return $RS
}
function GetReport($RS, $reportPath)
{
# Next we need to load the report. Since Powershell cannot pass a null string
# (it instead just passses ""), we have to use GetMethod / Invoke to call the
# function that returns the report object. This will load the report in the
# report server object, as well as create a report object that can be used to
# discover information about the report. It's not used in this code, but it can
# be used to discover information about what parameters are needed to execute
# the report.
$reportPath = "/" + $reportPath
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
# initialise empty parameter holder
$parameters = #()
$RS.SetExecutionParameters($parameters, "nl-nl") > $null
return $report
}
function AddParameter($params, $name, $val)
{
$par = New-Object RS.ParameterValue
$par.Name = $name
$par.Value = $val
$params += $par
return ,$params
}
function GetReportInFormat($RS, $report, $params, $outputpath, $format)
{
# Set up some variables to hold referenced results from Render
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
# Report parameters are handled by creating an array of ParameterValue objects.
# Add the parameter array to the service. Note that this returns some
# information about the report that is about to be executed.
# $RS.SetExecutionParameters($parameters, "en-us") > $null
$RS.SetExecutionParameters($params, "nl-nl") > $null
# Render the report to a byte array. The first argument is the report format.
# The formats I've tested are: PDF, XML, CSV, WORD (.doc), EXCEL (.xls),
# IMAGE (.tif), MHTML (.mhtml).
$RenderOutput = $RS.Render($format,
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
# Determine file name
$parts = $report.ReportPath.Split("/")
$filename = $parts[-1] + "."
switch($format)
{
"EXCEL" { $filename = $filename + "xls" }
"WORD" { $filename = $filename + "doc" }
"IMAGE" { $filename = $filename + "tif" }
default { $filename = $filename + $format }
}
if($outputpath.EndsWith("\\"))
{
$filename = $outputpath + $filename
} else
{
$filename = $outputpath + "\" + $filename
}
$filename
# Convert array bytes to file and write
$Stream = New-Object System.IO.FileStream($filename), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
}
$RS = GetRSConnection -server "DEVBOX" -instance "ReportServer_DEV"
$report = GetReport -RS $RS -reportPath "folder name/report name"
$params = #()
$params = AddParameter -params $params -name "Month" -val "201311"
GetReportInformat -RS $RS -report $report -params $params -outputpath "i:\test" -format "EXCEL"
Using web request:
[string]$Domain = "DomainUsername"
[string]$Username = "Username"
[string]$Password = "Password"
[string]$ReportServer = "http://ssrsreportserver/ReportServer/ReportExecution2005.asmx" #Report Server
[string]$ReportLocation = "/Report Location/Report Name" #Report Location ON SSRS
$ReportLocation = $ReportLocation.Replace("/", "%2f")
$ReportLocation = $ReportLocation.Replace(" ", "+")
[string]$outputFile = $PSScriptRoot + '\Report.xlsx' #Save location for the file
#If the report has any parameters
[string]$ParamString = "";
$ParamString += "¶m1=paramvalue"
$ParamString += "¶m2=paramvalue"
[string]$URL = $ReportServer + "?" + $ReportLocation + "&rs:Command=Render&rs:Format=" + "EXCELOPENXML" + "&rs:ParameterLanguage=en-GB" + $ParamString
Write-Host $URL
$Req = [System.Net.WebRequest]::Create($URL);
$Req.Credentials = new-object System.Net.NetworkCredential($Username, $Password, $Domain)
$Req.Timeout = 30000;
$WebStream = $Req.GetResponse().GetResponseStream();
$MemStream = New-Object System.IO.MemoryStream
$WebStream.CopyTo($MemStream);
[long]$Len = $MemStream.Length;
[byte[]]$outBytes = [System.Byte[]]::CreateInstance([System.Byte], $Len)
$MemStream.Seek(0, [System.IO.SeekOrigin]::Begin);
$MemStream.Read($outBytes, 0, [int]$Len);
$WebStream.Close();
$MemStream.Close();
$MemStream.Dispose();
$Stream = New-Object System.IO.FileStream($outputFile), Create, Write
$Stream.Write($outBytes, 0, $outBytes.Length)
$Stream.Close()
Invoke-Item $outputFile
I'm attempting to create a function for a script module that verifies the previous parameter has been set before it shows the next parameter.
The parameters I need are Identity, Share and Quota. I always want Identity to show, I don't want Share to show until the Identity has been set and I don't want Quota to show until the Share has been set.
I'm able to easily access $Identity, I'm not able to access $Share from within DynamicParam{}. I stepped through the script using PowerGUI and I was only able to see $Share when I hit Begin{}.
I have a way to workaround this by just showing Share/Quota if Identity is set, but ultimately I would like to learn how to keep adding additional parameters based on the previously set parameter.
A copy of the function is below. personfileutility.exe is just an executable we use to interact with the various systems to provision and gather information on a user.
function New-PersonalFiles
{
[CmdletBinding()]
Param
(
# Param1 help description
[Parameter(Mandatory=$true)]
$Identity
)
DynamicParam
{
$paramDictionary = new-object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
if (($Identity -notlike "") -or ($Identity -notlike $null)){
$attributes = new-object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "\\fileserver\sharename"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "\\fileserver\sharename"
$arguments += "\\fileserver\sharename2"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "\\fileserver2\sharename"
}
$ParamOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments
$attributeCollection = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$attributeCollection.Add($ParamOptions)
$dynParam1 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Share", [String], $attributeCollection)
$paramDictionary.Add("Share", $dynParam1)
}
if (($Share -like "\\fileserver\*"))
{
$attributes2 = new-object System.Management.Automation.ParameterAttribute
$attributes2.ParameterSetName = "__AllParameterSets"
$attributes2.Mandatory = $true
$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
$type = $lookup.User.user_directory.type.type
if ($type -like "other" -or $type -like "staff") {
$arguments = #()
$arguments += "15GB"
$arguments += "20GB"
}
elseif ($type -like "faculty") {
$arguments = #()
$arguments += "10GB"
$arguments += "15GB"
}
elseif ($type -like "student") {
$arguments = #()
$arguments += "5GB"
$arguments += "10GB"
}
$ParamOptions2 = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $arguments2
$attributeCollection2 = new-object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection2.Add($attributes2)
$attributeCollection2.Add($ParamOptions2)
$dynParam2 = new-object -Type System.Management.Automation.RuntimeDefinedParameter("Quota", [String], $attributeCollection2)
$paramDictionary.Add("Quota", $dynParam2)
}
return $paramDictionary
}
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
Begin
{
}
Process
{
\\servername\personalfiles\personfileutility.exe -a $Identity -c -q ((Invoke-Expression $PSBoundParameters.Quota) / 1KB) -s $Share
}
End
{
}
}
I'm trying to replicate the "workaround" you're using, but I'm only seeing that I have access to the dynamic parameter Share (not Quota). In order to reproduce your setup, since I don't have access to personfileutility.exe I have commented out two lines and added a third where I hardcode $type to "faculty". For example, in two places I have updated the code to be:
#$lookup = [xml](\\servername\personalfiles\personfileutility.exe -a $Identity)
#$type = $lookup.User.user_directory.type.type
$type = "faculty"
With these changes in place, I can access the Share parameter after I specify an Identity. However, I cannot access the Quota. Do you expect Quota to be available?
If I'm understanding the problem you're asking about, you do not want Share to be accessible until Identity is accessible (which you currently have working). But you in addition, you don't want Quota to be accessible until both Identity and Share are filled out and you don't know how to make that work. Is that correct?
If I'm understanding the problem correctly, I don't believe that PowerShell offers a mechanism to achieve that with Commandlet binding. I think you could make that work by either using a GUI application, or interactively prompting the user for inputs.
I've been toying with this for days with no luck. Essentially I'm trying to build a simple library to render SSRS reports using Powershell. I'm using Powershell in an attempt to ease development later on (Instead of coding a C# app for each project). Mostly this will be used to schedule various things with reports.
I've got report rendering mostly working in Powershell. The one thing I can't figure out is how to supply parameters to the report before calling the render method. I've found plenty of code pertaining to C# and VB (which I've used in other SSRS projects), however I'm unable to convert this to Powershell.
As I'm fairly new to Powershell, I'm unfamiliar with the proper way to do this. Here's the code I've been using:
$ReportExecutionURI = "http://glitas10//ReportServer//ReportExecution2005.asmx?wsdl"
$ReportPath = "/Financial/ExpenseReportStub"
$format = "PDF"
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
$Reports = New-WebServiceProxy -Uri $ReportExecutionURI -UseDefaultCredential
# Load the report
$Report = $Reports.GetType().GetMethod("LoadReport").Invoke($Reports, #($ReportPath, $null))
# Render the report
$RenderOutput = $Reports.Render($format, $deviceInfo, [ref] $extension, [ref] $mimeType, [ref] $encoding, [ref] $warnings, [ref] $streamIDs)
That works fine on reports that don't require parameters, obviously.
Any ideas on what I need to do to instantiate the proper object and pass parameters?
Here's some information on the solution that I ended up using, in case anyone else needs to do the same. It works really well.
The first approach that worked was building a DLL to use by the Powershell script. This worked fine, but it causes two problems. First, your script had to tote around a DLL. Second, this DLL was tied to a specific SSRS server. In order to access another server, you had to use multiple DLLs.
Eventually, I moved back to using a web proxy. The key here is to use namespaces so that you can instantiate a ParameterValue object. Here's the code:
# Create a proxy to the SSRS server and give it the namespace of 'RS' to use for
# instantiating objects later. This class will also be used to create a report
# object.
$reportServerURI = "http://<SERVER>/ReportServer/ReportExecution2005.asmx?WSDL"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -UseDefaultCredential
$RS.Url = $reportServerURI
# Set up some variables to hold referenced results from Render
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
# Next we need to load the report. Since Powershell cannot pass a null string
# (it instead just passses ""), we have to use GetMethod / Invoke to call the
# function that returns the report object. This will load the report in the
# report server object, as well as create a report object that can be used to
# discover information about the report. It's not used in this code, but it can
# be used to discover information about what parameters are needed to execute
# the report.
$reportPath = "/PathTo/Report"
$Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
# Report parameters are handled by creating an array of ParameterValue objects.
$parameters = #()
$parameters += New-Object RS.ParameterValue
$parameters[0].Name = "Parameter 1"
$parameters[0].Value = "Value"
$parameters += New-Object RS.ParameterValue
$parameters[1].Name = "Parameter 2"
$parameters[1].Value = "Value"
# Add the parameter array to the service. Note that this returns some
# information about the report that is about to be executed.
$RS.SetExecutionParameters($parameters, "en-us") > $null
# Render the report to a byte array. The first argument is the report format.
# The formats I've tested are: PDF, XML, CSV, WORD (.doc), EXCEL (.xls),
# IMAGE (.tif), MHTML (.mhtml).
$RenderOutput = $RS.Render('PDF',
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs
)
# Convert array bytes to file and write
$Stream = New-Object System.IO.FileStream("output.pdf"), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
It seems rather easy, and it is. This method works exceptionally well and is the method I'm using now to render and email scheduled reports, as it provides much more flexibility than the built in SSRS scheduling. In addition, it's relatively fast. One of the scripts I'm using to mail out reports can render and send out about 20-30 reports a minute.
I had a similar issue.
It took time to figure out the issue.
You should not "revoke" the report without parameters if needed
Therefore the code should look like this:
try {
<# Despose and clear resources if open #>
if ($RS) { $RS.Dispose() }
if ($Stream) { $Stream.Close() }
<# Create Report Service #>
[string]$reportServerURI = "<SSRS Service URL>"
$RS = New-WebServiceProxy -Class 'RS' -NameSpace 'RS' -Uri $reportServerURI -UseDefaultCredential
$RS.Url = $reportServerURI
<# Set up some variables to hold referenced results from Render #>
$deviceInfo = "<DeviceInfo><NoHeader>True</NoHeader></DeviceInfo>"
$extension = ""
$mimeType = ""
$encoding = ""
$warnings = $null
$streamIDs = $null
<# Initial Report #>
$reportPath = "<Full path/URL to rdl file>"
## Do not revoke the report ## $Report = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null))
<# Initial Report Parameters Array #>
$Parameters = $RS.GetType().GetMethod("LoadReport").Invoke($RS, #($reportPath, $null)).Parameters
<# Populate Report Parameters values #>
$Params = #()
Foreach ($Parameter in $Parameters ) {
$par1 = New-Object RS.ParameterValue;
$Par1.Name = $Parameter.Name;
$Par1.Label = $Parameter.Name;
switch ($Par1.Name) {
"<1st Param Name>" { $par1.Value = <1st Param Value>; break }
"<2nd Param Name>" { $par1.Value = <2nd Param Value>; break }
...
"<#n Param Name>" { $par1.Value = <#n Param Value>; break }
}
$Params += $Par1;
}
<# Execute/invoke the report with the parameters #>
$RS.SetExecutionParameters($Params, "en-us") > $null
<# Set report render output format#>
[string]$format = <"PDF","Excel" etc.>
<# Eecute Report render #>
try { $RenderOutput = $RS.Render($format,
$deviceInfo,
[ref] $extension,
[ref] $mimeType,
[ref] $encoding,
[ref] $warnings,
[ref] $streamIDs)
} catch { Log-Message -message "Unable to render or save the report due to an error." -IsError $true; throw
}
<# Convert array bytes to file and write #>
$Stream = New-Object System.IO.FileStream(<Final Report Output File), Create, Write
$Stream.Write($RenderOutput, 0, $RenderOutput.Length)
$Stream.Close()
if ($RS) { $RS.Dispose() }
}catch{ Log-Message -message "Error in Execute-Report, could not stream or other error." -IsError $true; throw }
Problem solved.
Had the same issue, furthermore wanted to send the generated MHT file as an email body:
The following was found to work
The old CDO.Message is the only thing I found that allows sending a MHTML file as an email body.
Below is a (working) translation of a VB program
Old but simple ;-)!
################## Send MHTML email ##############################
# use antiquated CDO to send mhtml as email body
$smtpServer = "my-mail-server"
$smtpSubject = "MHT file sent as body of email"
$smtpTo = "you#work.com"
$smtpFrom = "me#home.org"
$MHTMLfile = "my-MHT-File.mht
# e.g. from an SSRS.Render
$AdoDbStream = New-Object -ComObject ADODB.Stream
$AdoDbStream.Charset = "ascii"
$AdoDbStream.Open()
$AdoDbStream.LoadFromFile($MHTMLfile)
$CdoMessage = New-Object -ComObject CDO.Message
$CdoMessage.DataSource.OpenObject($AdoDbStream,"_Stream")
$SendUsingPort = 2
$smtpPort = 25
$cfg = "http://schemas.microsoft.com/cdo/configuration/"
$CdoMessage.Configuration.Fields.Item($cfg + "sendusing") = $SendUsingPort
$CdoMessage.Configuration.Fields.Item($cfg + "smtpserver") = $SmtpServer
$CdoMessage.Configuration.Fields.Item($cfg + "smtpserverport") = $smtpPort
$CdoMessage.To = $smtpTo
$CdoMessage.From = $smtpFrom
$CdoMessage.Subject = $smtpSubject
$CdoMessage.MimeFormatted = $true
$CdoMessage.Configuration.Fields.Update()
WRITE-HOST "Sending email"
$CdoMessage.Send()