We are developing some reports locally. These reports are to be installed on a customer's machine. But we cannot reach this machine online.
What I've managed so far:
A folder containing:
This script will create all needed folders within ReportServer, load the data source, then the data sets and finally the reports. This works, I can open the portal and can see my objects.
The downloaded rdl files had their data-set-references set to the plain dataset's name - why ever... It took me hours to find, that the reference must include the full (internal) path to the dataset... Now the paginated reports really work, heureka!
This does not work for
How can mobile reports be uploaded via PowerShell?
How can a custom branding package be uploaded?
Is there a better approach? I'm fairly new to PowerShell and therefore open to any advice! TIA
Deleted the script fragment as I've posted a comprehensive answer myself...
At the bottom of my own answer there are some open questions... I'd love to give the bounty to somebody but it seems to be lost... Anyway: If Anybode might answer at least part of the remaining open questions there's a good chance for 150 points :-)
To publish all reports in a projectOn the Build menu, click Deploy <report project name>. Alternatively, in Solution Explorer, right-click the report project and then click Deploy.
By Uploading RDL file in Report Server.Open SSRS Server from webportal URL. There, you will see the upload button. Click the upload option and browse the rdl file of the report from the location. It uploads your report to the report server.
Microsoft decided to let us poor tech staff alone with this...
As nobody seems to be able to help - even with a bounty! - I'll state what I've found so far:
The following PowerShellScript is a working stand-alone deployment on a disconnected machine.
$DeployDirectory=".\MyProduct Reports\"
$DbURL="1.2.3.4"
$srvURI="http://1.2.3.4/ReportServer"
$srvURL="http://1.2.3.4/reports"
#Write RS Report folders
$rootDir="/MyProduct Reports"
#Import the module and create folders. This might be automated too (out of the directory structure found on disc, but this is up to you...)
Import-Module .\ReportingServicesTools
New-RsFolder -ReportServerUri $srvURI -Path "/" -Name "MyProduct Reports"
New-RsFolder -ReportServerUri $srvURI -Path $rootDir -Name "MyProduct Data"
New-RsFolder -ReportServerUri $srvURI -Path $rootDir -Name "EventLog"
New-RsFolder -ReportServerUri $srvURI -Path $rootDir -Name "Public Reports"
New-RsFolder -ReportServerUri $srvURI -Path $rootDir -Name "Report Audit"
Write-Host "RS Report folders written"
#Create shared data source
New-RsDataSource -RsFolder ($rootDir + "/MyProduct Data") -Name "MyProduct_DatabaseConnection" -Extension "SQL" -ConnectionString ("Data Source=" + $DbURL + ";Integrated Security=True;Initial Catalog=master;") -CredentialRetrieval "Integrated"
Write-Host "Shared data source created"
#Modify shared data set files: The hardcoded reference to the server's URL must be changed
$allRSDs = Get-ChildItem -Recurse -Path $DeployDirectory | Where-Object -FilterScript {$_.Extension -eq ".rsd"}
Write-Host "RSDs fetched"
$xml = New-Object System.Xml.XmlDocument
$nsMngr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMngr.AddNamespace("rd","http://schemas.microsoft.com/SQLServer/reporting/reportdesigner")
$allRSDs | % {
$FileName=$_.FullName;
$xml.Load($FileName);
$xml.SelectNodes("//rd:ReportServerUrl",$nsMngr) |
% {$_.InnerText=$srvURI};
$newContent = $xml.InnerXml;
Set-Content -Path $FileName -Value $newContent
}
Write-Host "Shared data set files modified"
Write-RsFolderContent -Recurse -ReportServerUri $srvURI -Path ($DeployDirectory + "MyProduct Data") -RsFolder ($rootDir + "/MyProduct Data")
Write-Host "Shared DataSets created"
#Read all created shared data sets out of the database into a table variable
$con = New-Object System.Data.SqlClient.SqlConnection
$con.ConnectionString=("Data Source=" + $DbURL + ";Integrated Security=True;Initial Catalog=master;")
$con.Open();
Write-Host "connection opened"
$cmd = New-Object System.Data.SqlClient.SqlCommand
$cmd.Connection = $con
Write-Host "command created"
$cmd.CommandText = "SELECT ItemID, [name] AS DataSetName, [Path] AS DataSetPath, LEN([name]) AS SortOrder `
FROM ReportServer.dbo.[Catalog]`
WHERE [Type]=8"
$adapt = New-Object System.Data.SqlClient.SqlDataAdapter
$adapt.SelectCommand = $cmd
$ds = New-Object System.Data.DataSet
$adapt.Fill($ds)
$allDs = New-Object System.Data.DataTable
$allDs = $ds.Tables[0]
Write-Host "shared datasets fetched into cache"
Class shDs {[string]$ItemId=""; [string]$DataSetName=""; [string]$DataSetPath="";}
function Get-SharedDataSet([string]$DataSetName){
$retVal = New-Object shDs
$Search = ("'" + $DataSetName + "' LIKE DataSetName + '%'")
$Sort = ("SortOrder DESC")
$dsRow = $allDs.Select($Search,$Sort)[0]
$retVal.ItemID=$dsRow["ItemID"].ToString().Trim()
$retVal.DataSetPath=$dsRow["DataSetPath"].ToString().Trim()
$retVal.DataSetName=$dsRow["DataSetName"].ToString().Trim()
return $retVal
}
Write-Host "function to fetch referenced shared dataset created"
$con.Close()
Write-Host "connection closed"
#Modify paginated report files: The newly written shared datasets must be written into the report-XML
$allRDLs = (Get-ChildItem -Recurse -Path $DeployDirectory |
Where-Object -FilterScript {$_.Extension -eq ".rdl"})
$xml = New-Object System.Xml.XmlDocument
$nsMngr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMngr.AddNamespace("rd","http://schemas.microsoft.com/SQLServer/reporting/reportdesigner")
$nsMngr.AddNamespace("ns","http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition")
$allRDLs | % {
$FileName=$_.FullName;
$xml.Load($FileName);
$xml.SelectNodes("//rd:ReportServerUrl",$nsMngr) |
% {$_.InnerText=$srvURI};
$xml.SelectNodes("//ns:SharedDataSetReference",$nsMngr) |
% {
$it = ("/" + $_.Innertext);
$ref=$it.SubString($it.LastIndexOf("/")+1);
$ds = Get-SharedDataSet($ref);
$_.InnerText=$ds.DataSetPath
Write-Host ("DataSetPath: " + $_.InnerText)
};
$newContent = $xml.InnerXml;
Set-Content -Path $FileName -Value $newContent
}
Write-Host "paginated report files modified"
Write-RsFolderContent -Recurse -ReportServerUri $srvURI -Path ($DeployDirectory + "EventLog") -RsFolder ($rootDir + "/EventLog")
Write-RsFolderContent -Recurse -ReportServerUri $srvURI -Path ($DeployDirectory + "Public Reports") -RsFolder ($rootDir + "/Public Reports")
Write-RsFolderContent -Recurse -ReportServerUri $srvURI -Path ($DeployDirectory + "Report Audit") -RsFolder ($rootDir + "/Report Audit")
Write-Host "paginated reports created"
#Modify mobile report files: This is more complicated... The files are ZIPs actually. These ZIPs contain several files. The metadata.xml
and the sources.xml
contain hard-coded references and must be changed
$allMobs = (Get-ChildItem -Recurse -Path $DeployDirectory |
Where-Object -FilterScript {$_.Extension -eq ".rsmobile"})
#Unzip SomeName.rsmobile into SomeName.rsmobile.Unzipped
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.Collections
$unzippedList = New-Object System.Collections.ArrayList
Class zippedMobs {[string]$DirectoryName; [string]$rsMobileName; [string]$FileName;}
Get-ChildItem -Recurse $path |
Where-Object -FilterScript {$_.Extension -eq ".rsmobile"} |
% {
$zm = New-Object zippedMobs;
$zm.DirectoryName = ($_.FullName + ".Unzipped");
$zm.rsMobileName=$_.FullName;
$zm.FileName=$_.Name;
$unzippedList.Add($zm);
[System.IO.Compression.ZipFile]::ExtractToDirectory($zm.rsMobileName,$zm.DirectoryName)
}
Write-Host "Mobile Reports: Files unzipped"
#Open all metadata.xml files in all unzipped folders and modify them
$xml = New-Object System.Xml.XmlDocument
$nsMngr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMngr.AddNamespace("ns","http://schemas.microsoft.com/sqlserver/reporting/2016/02/mobilereportpackage")
$nsMngr.AddNamespace("mrp","http://schemas.microsoft.com/sqlserver/reporting/2016/02/mobilereportpublisher")
$unzippedList | % {
$FileName=($_.DirectoryName + "\metadata.xml");
$xml.Load($FileName);
$xml.SelectNodes("//ns:dataSet",$nsMngr) |
% {
$ref=$_.Attributes["name"].Value;
$ds = Get-SharedDataSet($ref);
$_.Attributes["mrp:Server"].Value=$srvURL;
$_["id"].InnerText=$ds.ItemID;
$_["path"].InnerText=$ds.DataSetPath
};
$newContent = $xml.InnerXml;
Set-Content -Path $FileName -Value $newContent
}
Write-Host "Mobile Reports: All metadata.xml re-mapped"
#Open all sources.xml files in all unzipped folders and modify them
$xml = New-Object System.Xml.XmlDocument
$unzippedList | % {
$FileName=($_.DirectoryName + "\sources.xml");
$xml.Load($FileName);
$xml.SelectNodes("//Shared") |
% {
$ref=$_.Attributes["Name"].Value;
$ds = Get-SharedDataSet($ref);
$_.Attributes["ServerUri"].Value=$srvURL;
$_.Attributes["Guid"].Value=$ds.ItemID;
$_.Attributes["DataItemLocation"].Value=$ds.DataSetPath
};
$newContent = $xml.InnerXml;
Set-Content -Path $FileName -Value $newContent
}
Write-Host "Mobile Reports: All sources.xml re-mapped"
#Rename all original .rsmobile files
$unzippedList | % {Rename-Item -Path $_.rsMobileName -NewName ($_.FileName + ".old")}
Write-Host "Mobile Reports: Renamed all orginal .rsmobile files"
#Create new ZIP file for all mobile reports
$unzippedList | % {
[System.IO.Compression.ZipFile]::CreateFromDirectory($_.DirectoryName,$_.rsMobileName,[System.IO.Compression.CompressionLevel]::NoCompression, $FALSE)
}
Write-Host "Re-created all mobile report files"
Allthough the created ZIP-files (the new .rsmobile
files) are valid and contain the correct content, it is impossible to upload them via SSRS portal (error: invalid report package). But - funny enough! - when you use explorer's send to compressed directory
and rename the resulting ZIP file accordingly, this can be uploaded.
.rsmobile
(ZIP-file) that it is uploadable? (must be the same way of zipping as windows does it implicitly when sending to a compressed folder)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With