It all began with a client who wanted to distribute reports with minimal effort and make them usable by non-technical users. Minimal requirements were following:
So the hunt to a solution was on! I’ve found some scripts that did the trick by using PowerShell in combination with RSS. But after some trial (and lots of errors) these didn’t seem to include all the functionalities I wanted to provide to the client.
After some researching the web I encountered this blogpost on TechNet by Srinivase Rao Gude. I’d found my sweet, sweet nectar. Still, this solution needed some additional work, to make it more generic, more honey flavored.
First up, I wanted the Shell to demand information from the user. This way, it wasn’t a fix for only one environment or client. The command to prompt users information from PowerShell:
Read-Host –Prompt
All values that are hardcoded in the script are changed into variables that are loaded via the prompted information.
In the process of testing on several machines, the PowerShell wasn’t usable before changing the Execution Policy. Therefore I’ve added some additional code in the script that either works or doesn’t via the script itself. I haven’t found the actual reason why sometimes it just accepts it and other times it doesn’t. However, when it doesn’t work right away, Copy-Paste the following piece of code and execute this before getting crazy with the script.
#Setting PowerShell Execution Policy
<# Syntax
-ExecutionPolicy Policy
A new execution policy for the shell.
Bypass
Nothing is blocked and there are no warnings or prompts.
-Force
Suppress all prompts.
By default, Set-ExecutionPolicy displays a warning whenever the execution policy is changed.
-Scope ExecutionPolicyScope
The scope of the execution policy.
Process <Affect only the current PowerShell process.
#>
Set-ExecutionPolicy -scope Process -executionpolicy Bypass -force
I’ve chosen for myself to put the reports into the same directory under a folder called Reports.
As a final step I’ve added a shell command that opens Internet Explorer which opens the reporting URL to let the user see and taste the honey! It’s not the fasted code but it does the trick. You can find the code I’ve modified from Gude as source below.
Top tip: put the code into a flatfile (.txt) and rename it .ps1
With a right click and choosing the option to run with PowerShell, you can have a easy to use solution. Have fun with your automated SSRS deployment!!
:
try{
Write-Output "This script only runs in Powershell 2.0 or above"
#Requires -Version 2.0
}
catch {
$valError = $_.Exception.Message;
echo $_.Exception.Message;
pause
}
#Original Source: http://social.technet.microsoft.com/wiki/contents/articles/34521.powershell-script-for-ssrs-project-deployment.aspx
#Setting PowerShell Execution Policy
<# Syntax -ExecutionPolicy Policy A new execution policy for the shell. Bypass Nothing is blocked and there are no warnings or prompts. -Force Suppress all prompts. By default, Set-ExecutionPolicy displays a warning whenever the execution policy is changed. -Scope ExecutionPolicyScope The scope of the execution policy. Process Affect only the current PowerShell process. #>
Set-ExecutionPolicy -scope Process -executionpolicy Bypass -force
#Checking if User has Admin Rights
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs;
exit
}
#Set variables with configure values
$CurrentWorkingFolder = split-path -parent $MyInvocation.MyCommand.Definition
$Environment = "DEV" #specifiying Dev/Test/Prod Environment
$reportPath ="/" # Rool Level
#$SourceDirectory = "E:\Test" # Local drive where actual RDL/RDS/RSD files contains
$SourceDirectory = "$CurrentWorkingFolder\Reports"
# Set server IP address based on Environment provided from Config file
IF( $Environment -eq "DEV")
{
$webServiceUrl = Read-Host -Prompt 'Reporting Host URL (Http://hostname or Http://xxx.xxx.xx.xx)' # you can also give like "Http://xxx.xxx.xx.xx"
$RPServerName = Read-Host -Prompt 'Reporting server name (ReportServer)'#"ReportServer"
$reportFolder = Read-Host -Prompt 'Folder to be deployed TO'
$DataSourcePath = "Data Sources" #Folder where we need to create Datasource
$DataSet_Folder = "Datasets" # Folder where we need to create DataSet
}
<#ELSEIF ( $Environment -eq "TEST") { $webServiceUrl = "http://localhost" $RPServerName = "ReportServer" $reportFolder = "TEST_DemoReports" $DataSourcePath = "/TEST_DemoReports" #Folder where we need to create Datasource $DataSet_Folder = "/TEST_DemoReports" # Folder where we need to create DataSet } ELSEIF ($Environment -eq "PROD") { $webServiceUrl = "http://localhost" $RPServerName = "ReportServer" $reportFolder = "PROD_DemoReports" $DataSourcePath = "/PROD_DemoReports" #Folder where we need to create Datasource $DataSet_Folder = "/PROD_DemoReports" # Folder where we need to create DataSet }#>
#Overwrite properties
$IsOverwriteDataSource = switch (Read-Host -Prompt 'Overwrite DataSources? [Y],[N]')
{
"Y" {[Boolean] 1}
default {[Boolean] 0}
}
$IsOverwriteDataSet = switch (Read-Host -Prompt 'Overwrite DataSet? [Y],[N]')
{
"Y" {[Boolean] 1}
default {[Boolean] 0}
}
$IsOverwriteReport = switch (Read-Host -Prompt 'Overwrite Reports? [Y],[N]')
{
"N" {[Boolean] 0}
default {[Boolean] 1}
}
#Connecting to SSRS
Write-Host "Reportserver: $webServiceUrl" -ForegroundColor Magenta
Write-Host "Creating Proxy, connecting to : $webServiceUrl/$RPServerName/ReportService2010.asmx?WSDL" #Version SQL2012
Write-Host ""
$ssrsProxy = New-WebServiceProxy -Uri $webServiceUrl'/'$RPServerName'/ReportService2010.asmx?WSDL' -UseDefaultCredential
$reportFolder_Final = $reportPath + $reportFolder
$dataSourceFolder_Final = $reportPath + $DataSourcePath
$dataSetFolder_Final = $reportPath + $DataSet_Folder
##########################################
#Create Report Folder
Write-host ""
try
{
$ssrsProxy.CreateFolder($reportFolder, $reportPath, $null)
Write-Host "Created new folder: $reportFolder_Final"
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $reportFolder already exists."
}
else
{
$msg = "Error creating folder: $reportFolder. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
##########################################
#Create DataSource Folder
Write-host ""
try
{
$ssrsProxy.CreateFolder($DataSourcePath, $reportPath, $null)
Write-Host "Created new folder: $dataSourceFolder_Final"
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $DataSourcePath already exists."
}
else
{
$msg = "Error creating folder: $DataSourcePath. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
##########################################
#Create DataSet Folder
Write-host ""
try
{
$ssrsProxy.CreateFolder($DataSet_Folder, $reportPath, $null)
Write-Host "Created new folder: $dataSetFolder_Final"
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $DataSet_Folder already exists."
}
else
{
$msg = "Error creating folder: $DataSet_Folder. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
##########################################
#Create datasource
foreach($rdsfile in Get-ChildItem $SourceDirectory -Filter *.rds)
{
Write-host $rdsfile
#create data source
try
{
$rdsf = [System.IO.Path]::GetFileNameWithoutExtension($rdsfile);
$RdsPath = $SourceDirectory+"\"+$rdsf+".rds"
Write-host "Reading data from $RdsPath"
[xml]$Rds = Get-Content -Path $RdsPath
$ConnProps = $Rds.RptDataSource.ConnectionProperties
$type = $ssrsProxy.GetType().Namespace
$datatype = ($type + '.DataSourceDefinition')
$datatype_Prop = ($type + '.Property')
$DescProp = New-Object($datatype_Prop)
$DescProp.Name = 'Description'
$DescProp.Value = ''
$HiddenProp = New-Object($datatype_Prop)
$HiddenProp.Name = 'Hidden'
$HiddenProp.Value = 'false'
$Properties = @($DescProp, $HiddenProp)
$Definition = New-Object ($datatype)
$Definition.ConnectString = $ConnProps.ConnectString
$Definition.Extension = $ConnProps.Extension
if ([Convert]::ToBoolean($ConnProps.IntegratedSecurity)) {
$Definition.CredentialRetrieval = 'Integrated'
}
$DataSource = New-Object -TypeName PSObject -Property @{
Name = $Rds.RptDataSource.Name
Path = $dataSourceFolder_Final + '/' + $Rds.RptDataSource.Name
}
<# if ($IsOverwriteDataSource -eq 1) { [boolean]$IsOverwriteDataSource = 1 } else { [boolean]$IsOverwriteDataSource = 0 } #>
$warnings = $ssrsProxy.CreateDataSource($rdsf, $dataSourceFolder_Final ,$IsOverwriteDataSource, $Definition, $Properties)
# Write-Host $warnings
}
catch [System.IO.IOException]
{
$msg = "Error while reading rds file : '{0}', Message: '{1}'" -f $rdsfile, $_.Exception.Message
Write-Error msgcler
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "DataSource: $rdsf already exists."
}
else
{
$msg = "Error uploading report: $rdsf. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
}
##########################################
# Create Dataset
Write-host "dataset changes start"
foreach($rsdfile in Get-ChildItem $SourceDirectory -Filter *.rsd)
{
Write-host ""
$rsdf = [System.IO.Path]::GetFileNameWithoutExtension($rsdfile)
$RsdPath = $SourceDirectory+'\'+$rsdf+'.rsd'
Write-Verbose "New-SSRSDataSet -RsdPath $RsdPath -Folder $DataSet_Folder"
$RawDefinition = Get-Content -Encoding Byte -Path $RsdPath
$warnings = $null
$Results = $ssrsProxy.CreateCatalogItem("DataSet", $rsdf, $dataSetFolder_Final, $IsOverwriteDataSet, $RawDefinition, $null, [ref]$warnings)
write-host "dataset created successfully"
}
#############################
#For each RDL file in Folder
foreach($rdlfile in Get-ChildItem $SourceDirectory -Filter *.rdl)
{
Write-host ""
#ReportName
$reportName = [System.IO.Path]::GetFileNameWithoutExtension($rdlFile);
write-host $reportName -ForegroundColor Green
#Upload File
try
{
#Get Report content in bytes
Write-Host "Getting file content of : $rdlFile"
$byteArray = gc $rdlFile.FullName -encoding byte
$msg = "Total length: {0}" -f $byteArray.Length
Write-Host $msg
Write-Host "Uploading to: $reportFolder_Final"
$type = $ssrsProxy.GetType().Namespace
$datatype = ($type + '.Property')
$DescProp = New-Object($datatype)
$DescProp.Name = 'Description'
$DescProp.Value = ''
$HiddenProp = New-Object($datatype)
$HiddenProp.Name = 'Hidden'
$HiddenProp.Value = 'false'
$Properties = @($DescProp, $HiddenProp)
#Call Proxy to upload report
$warnings = $null
$Results = $ssrsProxy.CreateCatalogItem("Report", $reportName,$reportFolder_Final, $IsOverwriteReport,$byteArray,$Properties,[ref]$warnings)
if($warnings.length -le 1)
{ Write-Host "Upload Success." -ForegroundColor Green
}
else
{ write-host $warnings
}
}
catch [System.IO.IOException]
{
$msg = "Error while reading rdl file : '{0}', Message: '{1}'" -f $rdlFile, $_.Exception.Message
Write-Error msg
}
catch [System.Web.Services.Protocols.SoapException]
{
$msg = "Error uploading report: $reportName. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
##########################################
##Change Datasource on report
$reportFullName = $reportFolder_Final+"/"+$reportName
Write "datasource record $reportFullName"
$rep = $ssrsProxy.GetItemDataSources($reportFullName)
$rep | ForEach-Object {
$proxyNamespace = $_.GetType().Namespace
$constDatasource = New-Object ("$proxyNamespace.DataSource")
$constDatasource.Item = New-Object ("$proxyNamespace.DataSourceReference")
$FinalDatasourcePath = $dataSourceFolder_Final+"/" + $($_.Name)
$constDatasource.Item.Reference = $FinalDatasourcePath
$_.item = $constDatasource.Item
$ssrsProxy.SetItemDataSources($reportFullName, $_)
Write-Host "Changing datasource `"$($_.Name)`" to $($_.Item.Reference)"
}
}
Write-host ""
Write-host " We have successfully Deployed SSRS Project" -ForegroundColor Magenta
Write-host ""
#Open IE
$RPServernameUI = $RPServerName.Replace('ReportServer','Reports')
Start-Process "iexplore.exe" $webServiceUrl'/'$RPServernameUI"/Pages/Folder.aspx"
© 2023 Kohera
Crafted by
© 2022 Kohera
Crafted by
Cookie | Duration | Description |
---|---|---|
ARRAffinity | session | ARRAffinity cookie is set by Azure app service, and allows the service to choose the right instance established by a user to deliver subsequent requests made by that user. |
ARRAffinitySameSite | session | This cookie is set by Windows Azure cloud, and is used for load balancing to make sure the visitor page requests are routed to the same server in any browsing session. |
cookielawinfo-checkbox-advertisement | 1 year | Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category. |
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
CookieLawInfoConsent | 1 year | CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie. |
elementor | never | The website's WordPress theme uses this cookie. It allows the website owner to implement or change the website's content in real-time. |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
Cookie | Duration | Description |
---|---|---|
__cf_bm | 30 minutes | Cloudflare set the cookie to support Cloudflare Bot Management. |
pll_language | 1 year | Polylang sets this cookie to remember the language the user selects when returning to the website and get the language information when unavailable in another way. |
Cookie | Duration | Description |
---|---|---|
_ga | 1 year 1 month 4 days | Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors. |
_ga_* | 1 year 1 month 4 days | Google Analytics sets this cookie to store and count page views. |
_gat_gtag_UA_* | 1 minute | Google Analytics sets this cookie to store a unique user ID. |
_gid | 1 day | Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously. |
ai_session | 30 minutes | This is a unique anonymous session identifier cookie set by Microsoft Application Insights software to gather statistical usage and telemetry data for apps built on the Azure cloud platform. |
CONSENT | 2 years | YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data. |
vuid | 1 year 1 month 4 days | Vimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. |
Cookie | Duration | Description |
---|---|---|
ai_user | 1 year | Microsoft Azure sets this cookie as a unique user identifier cookie, enabling counting of the number of users accessing the application over time. |
VISITOR_INFO1_LIVE | 5 months 27 days | YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface. |
YSC | session | Youtube sets this cookie to track the views of embedded videos on Youtube pages. |
yt-remote-connected-devices | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt-remote-device-id | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt.innertube::nextId | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
yt.innertube::requests | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
Cookie | Duration | Description |
---|---|---|
WFESessionId | session | No description available. |