使用 PowerShell 在客户的机器上部署 SSRS 报告
Deploy SSRS reports on a customer's machine with PowerShell
我们正在本地开发一些报告。这些报告将安装 在客户的机器上。但是我们无法在线连接到这台机器。
到目前为止我管理的内容:
一个文件夹包含:
- 所有报表对象(数据源、数据集和报表)
- 一份 PowerShell module ReportingServicesTools
- PowerShell 脚本
此脚本将在 ReportServer 中创建所有需要的文件夹,加载数据源,然后是数据集,最后是报告。 这有效,我可以打开门户并且可以看到我的对象。
更新:
下载的 rdl 文件将其数据集引用设置为普通数据集的名称——为什么……我花了几个小时才发现,引用必须包含数据集的完整(内部)路径。 .. 现在分页报告真的有用了,嘿嘿!
问题
这不适用于
- 移动报告(又名仪表板,但不是 PowerBI)
- 我们的品牌文件
如何通过 PowerShell 上传移动报告?
如何上传自定义品牌包?
有更好的方法吗?我是 PowerShell 的新手,因此愿意接受任何建议! TIA
编辑
删除了脚本片段,因为我自己发布了一个全面的答案...
更新:还没有答案?
在我自己的回答底部有一些悬而未决的问题......我很想把赏金给别人,但它似乎丢失了......无论如何:如果 Anybode 可能至少回答部分问题剩余未决问题很有可能获得 150 分:-)
Microsoft 决定让我们可怜的技术人员独自面对这个...
目前发现的问题:
- 删除共享数据集后,必须从头开始重新创建所有移动报告(不支持重新映射数据源)。这是 设计、read this, if you cannot believe it
- 仅支持部署到可访问的服务器(通过目标 URL)
- 目前不支持通过 SSRS 门户以外的方式上传移动报告
- 目前不支持通过 SSRS 门户上传品牌包
因为似乎没有人能够提供帮助 - 即使有赏金! - 我将说明到目前为止我发现的内容:
以下 PowerShellScript 是在断开连接的计算机上运行的独立部署。
假设:
- 所有 SSRS 对象(.rsd、.rdl 和 .rsmobile)都已下载并位于 "DeployDirectory" 下面的适当目录中
- 在我的例子中,所有共享数据集文件都在文件夹 "MyProduct Data"
中
- 在我的例子中,所有报告都位于子文件夹 "EventLog"、"Public Reports" 和 "Report Audit"
中
- 可以自动化文件夹结构,但在此脚本中,这部分已根据我的需要进行了硬编码。
- PowerShell-Module ReportingServicesTools 位于一个目录中
下面同名 "DeployDirectory"
- 共享数据集的名称是全局唯一的(这不是 SSRS 强制要求的)
祝你好运!
$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-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"
#创建共享数据源
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"
#修改共享数据集文件:必须更改对服务器 URL 的硬编码引用
$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"
#从数据库中读取所有创建的共享数据集到一个table变量中
$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"
#修改分页报表文件:新写入的共享数据集必须写入报表-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:这个比较复杂...文件实际上是ZIP文件。这些 ZIP 包含多个文件。 metadata.xml
和 sources.xml
包含硬编码引用,必须更改
$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"
#打开所有解压文件夹中的所有metadata.xml个文件并修改
$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"
#打开所有解压文件夹中的所有sources.xml个文件并修改
$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"
#重命名所有原始 .rsmobile 文件
$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"
关注
虽然创建的 ZIP 文件(新 .rsmobile
文件)有效且包含正确的内容,但无法通过 SSRS 门户上传它们(错误:报告包无效).但是 - 足够有趣! - 当您使用资源管理器的 send to compressed directory
并相应地重命名生成的 ZIP 文件时,可以上传。
还有未解决的问题:
- 如何创建可上传的
.rsmobile
(ZIP 文件)? (必须与 windows 相同的压缩方式,当 发送到压缩文件夹 时隐式执行)
- 如何以编程方式上传移动报告?
这个我可以同时回答(见第二个回答):
- 如何以编程方式上传品牌包?
上传品牌包
有了这个我终于成功上传了一个品牌包
此存储过程将读取和写入所有 Catalog
个条目:
CREATE PROCEDURE InstallBrandingPackage
(
--Arguments
@BrandingZIPPath VARCHAR(300)
,@BrandingColorsPath VARCHAR(300)
,@BrandingLogopath VARCHAR(300)
,@BrandingType VARCHAR(100)
,@BrandingName VARCHAR(100)
,@BrandingVersion VARCHAR(10)
)
AS
BEGIN
BEGIN TRANSACTION;
SET XACT_ABORT ON;
DECLARE @BrandingZIPName VARCHAR(150)=RIGHT(@BrandingZIPPath,CHARINDEX('\',REVERSE(@BrandingZIPPath))-1);
DECLARE @BrandingZIP VARBINARY(MAX);
DECLARE @BrandingColorsJSON VARBINARY(MAX);
DECLARE @BrandingLogoPNG VARBINARY(MAX);
CREATE TABLE #tmpBranding(FileType VARCHAR(100),Content VARBINARY(MAX));
DECLARE @cmd VARCHAR(MAX);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''zip'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingZIPPath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''colors'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingColorsPath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''logo'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingLogopath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @BrandingZIP=(SELECT Content FROM #tmpBranding WHERE FileType='zip');
SET @BrandingColorsJSON=(SELECT Content FROM #tmpBranding WHERE FileType='colors');
SET @BrandingLogoPNG=(SELECT Content FROM #tmpBranding WHERE FileType='logo');
--needed variables and IDs taken from System Resources
DECLARE @SystemResourceID UNIQUEIDENTIFIER
,@SystemResourcePath VARCHAR(500)
,@PolicyID UNIQUEIDENTIFIER
,@UserID UNIQUEIDENTIFIER;
SELECT TOP 1 @SystemResourceID=ItemID
,@SystemResourcePath=[Path]
,@PolicyID=PolicyID
,@UserID=CreatedByID
FROM ReportServer.dbo.[Catalog] WHERE [Name] = 'System Resources';
--Delete all existing
DELETE FROM ReportServer.dbo.[Catalog] WHERE [Path] LIKE '%' + @BrandingType + '%';
--New Variables
DECLARE @NewZipID UNIQUEIDENTIFIER = NEWID();
DECLARE @NewPathID UNIQUEIDENTIFIER = NEWID();
DECLARE @NewPath VARCHAR(100) = '/' + LOWER(CAST(NEWID() AS VARCHAR(100)));
DECLARE @NewPathName VARCHAR(100) ='fbac82c8-9bad-4dba-929f-c04e7ca4111f'; --It seems, that this special GUID is needed, otherwise no Logo is displayed
DECLARE @NewBrandID UNIQUEIDENTIFIER = NEWID();
DECLARE @ColorsID UNIQUEIDENTIFIER = NEWID();
DECLARE @LogoID UNIQUEIDENTIFIER = NEWID();
DECLARE @dt DATETIME=GETDATE();
DECLARE @BrandProperties NVARCHAR(MAX)=
CAST((
SELECT @BrandingType AS [Resource.Type]
,@BrandingName AS [Resource.Name]
,@BrandingVersion AS [Resource.Version]
,LOWER(CAST(@NewZipID AS VARCHAR(100))) AS [Resource.PackageId]
,LOWER(CAST(@ColorsID AS VARCHAR(100))) AS [Item.colors]
,LOWER(CAST(@LogoID AS VARCHAR(100))) AS [Item.logo]
FOR XML PATH('Properties'),TYPE
) AS NVARCHAR(MAX));
--Universal Brand
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewBrandID,@SystemResourcePath + '/' + @BrandingType, @BrandingType, @SystemResourceID,1
,NULL
,@BrandProperties
,0,@UserID,@dt,@UserID,@dt,NULL,@PolicyID,0,1);
--ZIP file dummy
DECLARE @currentPath VARCHAR(500) = @SystemResourcePath + '/' + @BrandingType + '/' + @BrandingZIPName;
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewZipID,@currentPath,@BrandingZIPName,@NewBrandID,3
,@BrandingZIP
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'application/octet-stream',@PolicyID,0,1);
--Brand path
SET @currentPath = @SystemResourcePath + '/' + @BrandingType + '/' + @NewPathName;
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewPathID,@currentPath,@NewPathName,@NewBrandID,1
,NULL
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,NULL,@PolicyID,0,1);
--colors
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@ColorsID,@currentPath + '/colors','colors',@NewPathID,3
,@BrandingColorsJSON
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'application/octet-stream',@PolicyID,0,1);
--logo
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@LogoID,@currentPath + '/logo','logo',@NewPathID,3
,@BrandingLogoPNG
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'image/png',@PolicyID,0,1);
COMMIT;
END
然后使用此 PowerShell 行我解压缩包、提取参数并调用过程:
#Unzip BrandingPackage
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.Collections
$BrandingPackagePath = ($PSScriptRoot + "\FrequentisReportCenter.zip")
$BrandingPackageUnzipped = ($PSScriptRoot + "\FrequentisReportCenter.zip.Unzipped")
[System.IO.Compression.ZipFile]::ExtractToDirectory($BrandingPackagePath,$BrandingPackageUnzipped)
Write-Host "BrandingPackage unzipped"
$xml = New-Object System.Xml.XmlDocument
$nsMngr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMngr.AddNamespace("ns","http://schemas.microsoft.com/sqlserver/reporting/2016/01/systemresourcepackagemetadata")
$xml.Load(($BrandingPackageUnzipped + "\" + "metadata.xml"))
$BrandingType=$xml["SystemResourcePackage"].Attributes["type"].Value
$BrandingVersion=$xml["SystemResourcePackage"].Attributes["version"].Value
$BrandingName=$xml["SystemResourcePackage"].Attributes["name"].Value
$PathColors=($BrandingPackageUnzipped + "\" + $xml.SelectNodes("//ns:Contents/ns:Item[@key='colors']",$nsMngr)[0].Attributes["path"].Value)
$PathLogo=($BrandingPackageUnzipped + "\" + $xml.SelectNodes("//ns:Contents/ns:Item[@key='logo']",$nsMngr)[0].Attributes["path"].Value)
#BrandingPackage values fetched
$cmd.CommandText="MyDatabase.dbo.InstallBrandingPackage `
@BrandingZIPPath=@ZIPPath,`
@BrandingColorsPath=@ColorsPath,`
@BrandingLogoPath=@LogoPath,`
@BrandingType=@Type,`
@BrandingName=@Name,`
@BrandingVersion=@Version"
$cmd.Parameters.AddWithValue("@ZIPPath",$BrandingPackagePath)
$cmd.Parameters.AddWithValue("@ColorsPath",$PathColors)
$cmd.Parameters.AddWithValue("@LogoPath",$PathLogo)
$cmd.Parameters.AddWithValue("@Type",$BrandingType)
$cmd.Parameters.AddWithValue("@Name",$BrandingName)
$cmd.Parameters.AddWithValue("@Version",$BrandingVersion)
$cmd.ExecuteNonQuery()
#BrandingPackage written
我们正在本地开发一些报告。这些报告将安装 在客户的机器上。但是我们无法在线连接到这台机器。
到目前为止我管理的内容:
一个文件夹包含:
- 所有报表对象(数据源、数据集和报表)
- 一份 PowerShell module ReportingServicesTools
- PowerShell 脚本
此脚本将在 ReportServer 中创建所有需要的文件夹,加载数据源,然后是数据集,最后是报告。 这有效,我可以打开门户并且可以看到我的对象。
更新:
下载的 rdl 文件将其数据集引用设置为普通数据集的名称——为什么……我花了几个小时才发现,引用必须包含数据集的完整(内部)路径。 .. 现在分页报告真的有用了,嘿嘿!
问题
这不适用于
- 移动报告(又名仪表板,但不是 PowerBI)
- 我们的品牌文件
如何通过 PowerShell 上传移动报告?
如何上传自定义品牌包?
有更好的方法吗?我是 PowerShell 的新手,因此愿意接受任何建议! TIA
编辑
删除了脚本片段,因为我自己发布了一个全面的答案...
更新:还没有答案?
在我自己的回答底部有一些悬而未决的问题......我很想把赏金给别人,但它似乎丢失了......无论如何:如果 Anybode 可能至少回答部分问题剩余未决问题很有可能获得 150 分:-)
Microsoft 决定让我们可怜的技术人员独自面对这个...
目前发现的问题:
- 删除共享数据集后,必须从头开始重新创建所有移动报告(不支持重新映射数据源)。这是 设计、read this, if you cannot believe it
- 仅支持部署到可访问的服务器(通过目标 URL)
- 目前不支持通过 SSRS 门户以外的方式上传移动报告
- 目前不支持通过 SSRS 门户上传品牌包
因为似乎没有人能够提供帮助 - 即使有赏金! - 我将说明到目前为止我发现的内容:
以下 PowerShellScript 是在断开连接的计算机上运行的独立部署。
假设:
- 所有 SSRS 对象(.rsd、.rdl 和 .rsmobile)都已下载并位于 "DeployDirectory" 下面的适当目录中
- 在我的例子中,所有共享数据集文件都在文件夹 "MyProduct Data" 中
- 在我的例子中,所有报告都位于子文件夹 "EventLog"、"Public Reports" 和 "Report Audit" 中
- 可以自动化文件夹结构,但在此脚本中,这部分已根据我的需要进行了硬编码。
- PowerShell-Module ReportingServicesTools 位于一个目录中 下面同名 "DeployDirectory"
- 共享数据集的名称是全局唯一的(这不是 SSRS 强制要求的)
祝你好运!
$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-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"
#创建共享数据源
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"
#修改共享数据集文件:必须更改对服务器 URL 的硬编码引用
$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"
#从数据库中读取所有创建的共享数据集到一个table变量中
$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"
#修改分页报表文件:新写入的共享数据集必须写入报表-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:这个比较复杂...文件实际上是ZIP文件。这些 ZIP 包含多个文件。 metadata.xml
和 sources.xml
包含硬编码引用,必须更改
$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"
#打开所有解压文件夹中的所有metadata.xml个文件并修改
$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"
#打开所有解压文件夹中的所有sources.xml个文件并修改
$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"
#重命名所有原始 .rsmobile 文件
$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"
关注
虽然创建的 ZIP 文件(新 .rsmobile
文件)有效且包含正确的内容,但无法通过 SSRS 门户上传它们(错误:报告包无效).但是 - 足够有趣! - 当您使用资源管理器的 send to compressed directory
并相应地重命名生成的 ZIP 文件时,可以上传。
还有未解决的问题:
- 如何创建可上传的
.rsmobile
(ZIP 文件)? (必须与 windows 相同的压缩方式,当 发送到压缩文件夹 时隐式执行) - 如何以编程方式上传移动报告?
这个我可以同时回答(见第二个回答):
- 如何以编程方式上传品牌包?
上传品牌包
有了这个我终于成功上传了一个品牌包
此存储过程将读取和写入所有 Catalog
个条目:
CREATE PROCEDURE InstallBrandingPackage
(
--Arguments
@BrandingZIPPath VARCHAR(300)
,@BrandingColorsPath VARCHAR(300)
,@BrandingLogopath VARCHAR(300)
,@BrandingType VARCHAR(100)
,@BrandingName VARCHAR(100)
,@BrandingVersion VARCHAR(10)
)
AS
BEGIN
BEGIN TRANSACTION;
SET XACT_ABORT ON;
DECLARE @BrandingZIPName VARCHAR(150)=RIGHT(@BrandingZIPPath,CHARINDEX('\',REVERSE(@BrandingZIPPath))-1);
DECLARE @BrandingZIP VARBINARY(MAX);
DECLARE @BrandingColorsJSON VARBINARY(MAX);
DECLARE @BrandingLogoPNG VARBINARY(MAX);
CREATE TABLE #tmpBranding(FileType VARCHAR(100),Content VARBINARY(MAX));
DECLARE @cmd VARCHAR(MAX);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''zip'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingZIPPath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''colors'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingColorsPath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @cmd=
'INSERT INTO #tmpBranding(FileType,Content)
SELECT ''logo'',BulkColumn FROM OPENROWSET(BULK ''' + @BrandingLogopath + ''', SINGLE_BLOB) AS x';
EXEC(@cmd);
SET @BrandingZIP=(SELECT Content FROM #tmpBranding WHERE FileType='zip');
SET @BrandingColorsJSON=(SELECT Content FROM #tmpBranding WHERE FileType='colors');
SET @BrandingLogoPNG=(SELECT Content FROM #tmpBranding WHERE FileType='logo');
--needed variables and IDs taken from System Resources
DECLARE @SystemResourceID UNIQUEIDENTIFIER
,@SystemResourcePath VARCHAR(500)
,@PolicyID UNIQUEIDENTIFIER
,@UserID UNIQUEIDENTIFIER;
SELECT TOP 1 @SystemResourceID=ItemID
,@SystemResourcePath=[Path]
,@PolicyID=PolicyID
,@UserID=CreatedByID
FROM ReportServer.dbo.[Catalog] WHERE [Name] = 'System Resources';
--Delete all existing
DELETE FROM ReportServer.dbo.[Catalog] WHERE [Path] LIKE '%' + @BrandingType + '%';
--New Variables
DECLARE @NewZipID UNIQUEIDENTIFIER = NEWID();
DECLARE @NewPathID UNIQUEIDENTIFIER = NEWID();
DECLARE @NewPath VARCHAR(100) = '/' + LOWER(CAST(NEWID() AS VARCHAR(100)));
DECLARE @NewPathName VARCHAR(100) ='fbac82c8-9bad-4dba-929f-c04e7ca4111f'; --It seems, that this special GUID is needed, otherwise no Logo is displayed
DECLARE @NewBrandID UNIQUEIDENTIFIER = NEWID();
DECLARE @ColorsID UNIQUEIDENTIFIER = NEWID();
DECLARE @LogoID UNIQUEIDENTIFIER = NEWID();
DECLARE @dt DATETIME=GETDATE();
DECLARE @BrandProperties NVARCHAR(MAX)=
CAST((
SELECT @BrandingType AS [Resource.Type]
,@BrandingName AS [Resource.Name]
,@BrandingVersion AS [Resource.Version]
,LOWER(CAST(@NewZipID AS VARCHAR(100))) AS [Resource.PackageId]
,LOWER(CAST(@ColorsID AS VARCHAR(100))) AS [Item.colors]
,LOWER(CAST(@LogoID AS VARCHAR(100))) AS [Item.logo]
FOR XML PATH('Properties'),TYPE
) AS NVARCHAR(MAX));
--Universal Brand
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewBrandID,@SystemResourcePath + '/' + @BrandingType, @BrandingType, @SystemResourceID,1
,NULL
,@BrandProperties
,0,@UserID,@dt,@UserID,@dt,NULL,@PolicyID,0,1);
--ZIP file dummy
DECLARE @currentPath VARCHAR(500) = @SystemResourcePath + '/' + @BrandingType + '/' + @BrandingZIPName;
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewZipID,@currentPath,@BrandingZIPName,@NewBrandID,3
,@BrandingZIP
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'application/octet-stream',@PolicyID,0,1);
--Brand path
SET @currentPath = @SystemResourcePath + '/' + @BrandingType + '/' + @NewPathName;
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@NewPathID,@currentPath,@NewPathName,@NewBrandID,1
,NULL
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,NULL,@PolicyID,0,1);
--colors
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@ColorsID,@currentPath + '/colors','colors',@NewPathID,3
,@BrandingColorsJSON
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'application/octet-stream',@PolicyID,0,1);
--logo
INSERT INTO ReportServer.dbo.[Catalog](ItemID,[Path],[Name],ParentID,[Type]
,Content
,Property
,[Hidden],CreatedByID,CreationDate,ModifiedByID,ModifiedDate
,MimeType,PolicyID,PolicyRoot,ExecutionFlag)
VALUES(@LogoID,@currentPath + '/logo','logo',@NewPathID,3
,@BrandingLogoPNG
,'<Properties />'
,0,@UserID,@dt,@UserID,@dt,'image/png',@PolicyID,0,1);
COMMIT;
END
然后使用此 PowerShell 行我解压缩包、提取参数并调用过程:
#Unzip BrandingPackage
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.Collections
$BrandingPackagePath = ($PSScriptRoot + "\FrequentisReportCenter.zip")
$BrandingPackageUnzipped = ($PSScriptRoot + "\FrequentisReportCenter.zip.Unzipped")
[System.IO.Compression.ZipFile]::ExtractToDirectory($BrandingPackagePath,$BrandingPackageUnzipped)
Write-Host "BrandingPackage unzipped"
$xml = New-Object System.Xml.XmlDocument
$nsMngr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMngr.AddNamespace("ns","http://schemas.microsoft.com/sqlserver/reporting/2016/01/systemresourcepackagemetadata")
$xml.Load(($BrandingPackageUnzipped + "\" + "metadata.xml"))
$BrandingType=$xml["SystemResourcePackage"].Attributes["type"].Value
$BrandingVersion=$xml["SystemResourcePackage"].Attributes["version"].Value
$BrandingName=$xml["SystemResourcePackage"].Attributes["name"].Value
$PathColors=($BrandingPackageUnzipped + "\" + $xml.SelectNodes("//ns:Contents/ns:Item[@key='colors']",$nsMngr)[0].Attributes["path"].Value)
$PathLogo=($BrandingPackageUnzipped + "\" + $xml.SelectNodes("//ns:Contents/ns:Item[@key='logo']",$nsMngr)[0].Attributes["path"].Value)
#BrandingPackage values fetched
$cmd.CommandText="MyDatabase.dbo.InstallBrandingPackage `
@BrandingZIPPath=@ZIPPath,`
@BrandingColorsPath=@ColorsPath,`
@BrandingLogoPath=@LogoPath,`
@BrandingType=@Type,`
@BrandingName=@Name,`
@BrandingVersion=@Version"
$cmd.Parameters.AddWithValue("@ZIPPath",$BrandingPackagePath)
$cmd.Parameters.AddWithValue("@ColorsPath",$PathColors)
$cmd.Parameters.AddWithValue("@LogoPath",$PathLogo)
$cmd.Parameters.AddWithValue("@Type",$BrandingType)
$cmd.Parameters.AddWithValue("@Name",$BrandingName)
$cmd.Parameters.AddWithValue("@Version",$BrandingVersion)
$cmd.ExecuteNonQuery()
#BrandingPackage written