SQL Server 2005 und 2008 auf einer Maschine: Welche DTEXEC.EXE? 30.05.2012

Nicolas Meseth
Nicolas Meseth, Senior eXpert

Bei einem Kunden werden aktuell alle BI-Anwendungen von SQL Server 2005 auf 2008 R2 migriert. Da eine solche Migration nicht von heute auf morgen über die Bühne geht, müssen die Entwickler in der Regel auch mit beiden Umgebungen gleichzeitig entwickeln können. Dazu sollte auf Entwicklerrechnern sowohl Visual Studio 2005 und 2008 (BIDS) wie auch lokale SQL Server in beiden Versionen installiert sein. An sich ist das auch kein Problem, beides läuft einwandfrei nebeneinander.

Wenn aber lokale auf dem Entwicklerrechner SSIS Pakete mit DTEXEC ausgeführt werden, z. B. um Unittest auszuführen, muss darauf geachtet werden, dass die richtige Version von der DTEXEC.EXE zur Ausführung verwendet wird. Ansonsten kommt es zu Problemen bei der Ausführung der Pakete. Wie aber wird entschieden, welche Version von DTEXEC verwendet wird?

Reihenfolge im Systempfad ändern

Bei der SQL Server Installation wird ein Eintrag im Systempfad angelegt, der auf den Ordner zeigt, in dem die DTEXEC.EXE liegt. Bei einer Standardinstallation ist das der Pfad "Microsoft SQL Server\90\DTS\Binn" für 2005 bzw. "Microsoft SQL Server\100\DTS\Binn" für 2008 (R2). Sind beide Versionen installiert gibt es auch beide Einträge. Beim Aufruf aus einem beliebigen Verzeichnis wird nun stets der Pfad verwendet, der als erstes im Systempfad eingetragen ist. Eine Lösung zur Verwendung der richtigen Version ist somit die Anpassung der Reihenfolge im Systempfad.

DTEXEC.EXE umbenennen

Eine einfachere und schnellere Methode ist die Umbenennung der DTEXEC.EXE, die gerade nicht verwendet werden soll, in z. B. DTEXEC90.EXE. Auf diese Weise wird nur eine - die richtige - Version überhaupt gefunden und somit muss die korrekte ausgeführt werden. Vorteil dieser Lösung ist zudem, dass sie sehr schnell rückgängig gemacht werden kann. Für noch mehr Komfort und weniger Fehleranfälligkeit kann man das Ganze auch in einem CMD-Skript verpacken und bei Bedarf aufrufen. So könnten die Umbennungen der 2005er bzw. 2008er DTEXEC-Versionen jeweils in einem Skript "PrepareUnitTestsWith200X.cmd" abgebildet werden.

Share |

UnitTests 1: Auf die Ausgabe kommt es an! 25.05.2012

Alexander Jung
Alexander Jung, Chief eXpert

Dass Unittests schreiben mittlerweile zum guten Ton in der Softwareentwicklung gehört, sollte sich herumgesprochen haben. Platt ausgedrückt: Tests zu haben ist besser als keine Tests zu haben. Aber Test ist nicht gleich Test.

Ich hatte kürzlich das Vergnügen in zwei sehr unterschiedlich gelagerten Projekten mit Tests konfrontiert zu werden, die primär von anderen Entwicklern geschrieben wurden…

Im einen Projekt ging es dabei um eine große Anzahl vorhandener Tests. Tests die über einen längeren Zeitraum und von unterschiedlichen Entwicklern erstellt wurden. Und da es sich um ein Redesign und Refactoring handelte, waren rote Tests zwischenzeitlich keine Seltenheit.

Bei dem anderen Projekt handelte es sich hingegen um eine Neuimplementierung, was natürlich das Testing mit einschließt. Hier traten ganz andere Fragestellungen zutage, etwa bzgl. der grundsätzlichen Teststrategie oder des Einsatzes von Mocking Frameworks. Und natürlich haben Themen eine andere Bedeutung wenn sie zunächst nur eine Hand voll Tests betreffen, als wenn es um dutzende ähnlich gelagerter Fälle geht.

Man kann allerdings einige Dinge von vorne herein ganz anders einplanen, wenn man die Probleme kennt. Deshalb möchte ich einige Erfahrungen hier wiedergeben.

Um Unklarheiten zu vermeiden: Mit “Tests” sind hier reguläre Testprojekte basierend auf MSTest in Visual Studio gemeint. Auch den Begriff “UnitTest” verwende ich eher lax und bezogen auf die Art der Implementierung.

Tests werden zwar geschrieben damit sie grün werden, so wirklich Interessant wird die Sache aber erst im gegenteiligen Fall. Daher soll es in diesem Beitrag schwerpunktmäßig um das Verhalten im Rot-Fall gehen, insbesondere darum, wie man sich die Diagnose vereinfachen kann.

Auf die Ausgaben achten!

Man ändert etwas Grundlegendes und lässt die Tests laufen. Alles grün? Alles gut. Einige Tests rot? … und nun?

Es ist eine typische Erfahrung, dass ein Test zwar aussagt, dass etwas schief gegangen ist – aber um herauszubekommen, was die eigentliche Ursache ist, muss man den Debugger bemühen, was immer auch mit Aufwand verbunden ist. Dabei kann man sich das Leben hier deutliche einfacher machen, wenn man mit den Asserts anders umgeht. Ganz typische Szenarien sind folgende:

  • Zu komplexe Prüfungen in einem Schritt
  • Mehrere Prüfungen in einem Schritt
  • Unklare Ausgaben

Im Detail…

Hinweis: Die Beispiele sind nicht aus den echten Projekten entnommen und auf das jeweilige Problem verkürzt. Sie gehen davon aus, das Daten aus einer Datenbank gelesen werden (was der “reinen Lehre” bzgl. Unittests natürlich widerspricht).

 

Zu komplexe Prüfungen in einem Schritt

Einfaches Beispiel: Daten werden zunächst gelesen, dann verglichen ob der korrekte Datensatz geliefert wurde:

   1: [TestMethod]
   2: public void TestGetUser()
   3: {
   4:     var data = new DataAccess();
   5:  
   6:     // we know the test user!
   7:     var expected = new User { ID = 10, UserName = "TestUser1", Vorname = "Alter", Nachname = "Egon" };
   8:     var actual = data.GetUserInformation("TestUser1");
   9:  
  10:     // Assert.AreEqual doesn't work, but we put the IsEqual method on the User class...
  11:     Assert.IsTrue(expected.IsEqual(actual));
  12: }

Und das Ergebnis des Testlaufs:

image

Das ist eindeutig, der Datensatz stimmt nicht mit dem erwarteten Datensatz überein. Problem dabei: Wo genau der Unterschied liegt wird mir nicht verraten.

Um sich den Vergleich der Entität zu vereinfachen wurde in der Klasse eine Vergleichsmethode implementiert. Grundsätzlich eine gute Idee, denn man will ja nicht immer alle Properties einzeln prüfen müssen:

   1: public partial class User
   2: {
   3:     public bool IsEqual(User other)
   4:     {
   5:         return this.ID == other.ID
   6:             && this.UserName == other.UserName
   7:             && this.Vorname == other.Vorname
   8:             && this.Nachname == other.Nachname;
   9:         // other props ommited for brevity....
  10:     }
  11: }

Dummerweise geht dabei aber die Information verloren, wo der Unterschied liegt. Aber warum nicht diese Vergleichsmethode im Testprojekt belassen und die dort verfügbaren Mechanismen nutzen?

   1: private static void AssertAreEqual(User expected, User actual)
   2: {
   3:     Assert.AreEqual(expected.ID, actual.ID, "User.ID");
   4:     Assert.AreEqual(expected.UserName, actual.UserName, "User.UserName");
   5:     Assert.AreEqual(expected.Vorname, actual.Vorname, "User.Vorname");
   6:     Assert.AreEqual(expected.Nachname, actual.Nachname, "User.Nachname");
   7:     // other props ommited for brevity....
   8: }

Die Schreibarbeit für den Entwickler ist die gleiche, aber der Effekt im Rot-Fall ein ganz anderer:

image

Ausgabe des Properties, samt erwartetem und falschem Wert. Ich muss hier keinen Debugger mehr starten, um zu erkennen, dass im Testcode ein Tippfehler ist (es muss natürlich “Alter Ego” heißen). Wären mir hier falsche Daten aus der Datenbank geliefert worden, wäre das ebenso schnell offensichtlich geworden.

Gerade bei Tests auf Datenkonstellationen (Lesen von Daten, aber auch fachliche Berechnungen oder Validierungen) ist die Aussage, dass etwas schief gegangen ist zwar sehr wichtig (das ist schließlich die Rechtfertigung dafür, den Test überhaupt zu schreiben). Aber mit etwas Sorgfalt hinsichtlich der Ausgabe dessen, was tatsächlich schief gegangen ist kann man sich die Diagnose deutlich einfacher machen.

Das Beispiel hier ist einfach gelagert. Aber bei ausreichend großer Anzahl von Tests, womöglich älter und von anderen Entwicklern geschrieben, kann sich das deutlich auf die Behebungsdauer auswirken. Vor allem, wenn die Fehler in Bereichen auftreten, in denen man sich nicht auskennt, und wenn nicht einer sondern 20 Tests gleichzeitig Rot werden. In diesen Fällen ist man für jede Information dankbar, die einem bei einer schnellen Diagnose hilft.

 

Mehrere Prüfungen in einem Schritt

Ähnlich gelagert ist die Variante, mehrere unterschiedliche Prüfungen in einem Schritt durchzuführen. Insbesondere mit LINQ fällt es sehr einfach, sehr kompakt sehr viele Dinge auf einmal zu tun.

Beispiel: Eine Entität die über einen bestimmten Wert definiert wird muss genau einmal vorhanden sein und wird von einer anderen Entität referenziert:

   1: [TestMethod]
   2: public void TestGetConsistentData()
   3: {
   4:     var data = new DataAccess();
   5:  
   6:     // we know the test user!
   7:     var user = data.GetUserInformation("TestUser1");
   8:     var balanceSheets = data.GetBalanceSheets(1);
   9:  
  10:     // user contains a reference to the one and only default balance sheet
  11:     Assert.IsTrue(balanceSheets.Where(item => item.Rolle == "Laufent").Single().ID == user.LaufendeBilanzID);
  12: }

Und das Ergebnis:

image

Exception? Hat also das Single() zugeschlagen? Oder war womöglich die User-Referenz nicht gesetzt? Die Meldung verrät uns das leider nicht, was für eine Diagnose natürlich schlecht ist.

Das eigentliche Problem ist hier, dass mehr wie eine Prüfung in einem Rutsch durchgeführt werden: Existenz, Eindeutigkeit und Übereinstimmung mit dem Verweis. Besser wäre:

   1: [TestMethod]
   2: public void TestGetConsistentData_Better()
   3: {
   4:     var data = new DataAccess();
   5:  
   6:     // we know the test user!
   7:     var user = data.GetUserInformation("TestUser1");                       
   8:     var balanceSheets = data.GetBalanceSheets(1);
   9:  
  10:     // user contains a reference to the one and only default balance sheet
  11:     var subset = balanceSheets.Where(item => item.Rolle == "Laufent");
  12:     // Check if exactly one balance sheet has been found
  13:     Assert.IsTrue(subset.Count() == 1, "Expect exactly one current balance sheet.");
  14:     // check if it is consistent
  15:     Assert.IsTrue(subset.Single().ID == user.LaufendeBilanzID, "IDs inconsistent.");    
  16: }

Mit folgendem Ergebnis:

image

Hier ist sofort klar, was die Fehlerursache ist. Ein Blick in die Datenbank und auf den Code sollte ganz schnell offensichtlich machen, dass man “laufent” mit ‘d’ schreibt. Die Zeit um die eigentliche Ursache mit dem Debugger herauszufinden hat man schon wieder gespart.

 

Unklare Ausgaben

Das letzte Beispiel zeigt gleich das nächste Problem auf: Kaum ist der Schreibfehler behoben bekomme ich folgende Ausgabe:

image

Die ID passt nicht. Schön. Und weiter? Muss ich wieder den Debugger anwerfen um herauszubekommen, warum das der Fall ist?

Tatsächlich ist dies das häufigste Problem über das ich gestolpert bin: Assert bietet eine ganze Reihe von Prüfmethoden. Statt aber die jeweils adäquate zu verwenden – und damit von den jeweiligen Eigenschaften zu profitieren – bin ich oft auf die Verwendung von Assert.IsTrue() gestoßen. Die Prüfung geht damit natürlich genauso einfach von der Hand, aber das ist mit einem Informationsverlust verbunden. Besser wäre:

   1: [TestMethod]
   2: public void TestGetConsistentData_EvenBetter()
   3: {
   4:     var data = new DataAccess();
   5:  
   6:     // we know the test user!
   7:     var user = data.GetUserInformation("TestUser1");
   8:     var balanceSheets = data.GetBalanceSheets(1);
   9:  
  10:     // user contains a reference to the one and only default balance sheet
  11:     var subset = balanceSheets.Where(item => item.Rolle == "Laufend");
  12:     // Check if exactly one balance sheet has been found
  13:     Assert.AreEqual(1, subset.Count(), "Expect exactly one current balance sheet.");
  14:     // check if it is consistent
  15:     Assert.AreEqual(user.LaufendeBilanzID, subset.Single().ID, "IDs inconsistent.");
  16: }

Hier hat “nur” der Austausch von Assert.IsTrue() gegen Assert.AreEqual() stattgefunden, aber die Ausgabe gibt mir deutlich mehr Information:

image

Das ist sofort ausreichend um in der Datenbank die entsprechenden Datensätze zu suchen und herauszufinden, was für die Inkonsistenz verantwortlich ist. In diesem Falle wurde die falsche User-ID verwendet.

Bei Assert.AreEqual() spielt übrigens die Reihenfolge eine wichtige Rolle: Zuerst der erwartete Wert, dann der tatsächliche. Auch das wird gerne mal nicht so genau genommen.

 

Fazit…

Gerade wenn man mit den ersten Tests anfängt und noch das ganze System im Überblick hat, mag vieles von dem hier angeführten überflüssig erscheinen. Aber wenn ausreichend Zeit vergangen ist wird man diese Tests als Regressionstests verwenden. Und sollten sie dann rot werden, hat man die Details längst vergessen. Oder der Test stammt aus einem Bereich, in dem man selbst keine Aktien hatte.

Dabei muss man gar nicht viel tun, um sich die Arbeit für diese spätere Zeit zu vereinfachen. Die Qualität der Testausgaben zu erhöhen erfordert lediglich etwas Sorgfalt, der Aufwand ist vernachlässigbar. Nur muss man sich die Notwendigkeit erst einmal bewusst machen. Der eigentlich Nutzen stellt sich erst in der Zukunft ein – aber das gilt für automatisierte Tests generell.

 

PS: Das war natürlich nur ein Aspekt zum Thema Unittests; vermutlich wird noch der eine oder andere Beitrag folgen…

Share |

Endlich online! SDX Unternehmensprofil auf Kununu.de 23.05.2012

Svenja Henß
Svenja Henß, Senior Assistant

Endlich online - unser SDX Unternehmensprofil auf kununu.de. :-)

Hier bekommt ihr einen Einblick in den Arbeitsalltag der eXperts und erfahrt, was wir von Bewerbern erwarten und warum es sich lohnt bei den eXperts einzusteigen.

Wir freuen uns auf euer Feedback.

http://www.kununu.com/de/all/de/bc/sdx

Kununu

Share |

JavaScript und “Guter Stil”? 21.05.2012

Sven Erik Matzen
Sven Erik Matzen, Chief eXpert

StyleCop entwickelt sich (glücklicherweise) immer mehr zum Standard-Tool, wenn es um Coding-Style in C# geht. Doch heutige Applikationen bestehen nicht mehr nur aus einer Sprache: im Web-Bereich gab es immer schon eine Teilung in JavaScript, CSS und HTML als "Quell-Code" einer Anwendung; auf dem Desktop wird mit Windows 8 ebenfalls JavaScript eine Rolle spielen. Wie geht man nun mit den immer größer werdenden Code-Bereichen in JavaScript um?

JavaScript ist Applikations-Quellcode und hat damit auch die gleichen Aufgaben und "Probleme" wie C#-Quellcode. An JavaScript niedrigere Qualitätsanforderungen zu stellen, als an C#-Quellcode lässt sich unter dieser Annahme schlecht rechtfertigen. Leider gibt es zur Gestaltung des Quellcodes in JavaScript mindestens genau so viele Meinungen, wie es Menschen gibt, die JavaScript-Quellcode schreiben.

Ein recht interessanter Artikel dazu findet sich unter http://addyosmani.com/blog/javascript-style-guides-and-beautifiers/, wobei ich nicht nur die genannten Style-Guides, sondern vor allem auch die Kommentare interessant finde – Coding-Style kann sehr emotional diskutiert werden.

Eine Auswahl an Style-Guides:

Welcher Style-Guide ist jetzt der richtige? Das lässt sich aktuell noch nicht sagen. Ich mag "Crock's Code Conventions For JavaScript", da er in einigen Bereichen den Vorschlägen von StyleCop (und damit einem von mir bereits eingesetztem Coding-Style) entspricht. Ein klarer Vorteil für einen der Guides wäre eine Tool-Unterstützung für Visual Studio - die habe ich bisher noch für keinen der Styles gefunden. ReSharper bietet die Möglichkeit, auch für JavaScript Guidelines zu definieren, ist aber kostenpflichtig und kommt wieder mit einem eigenen Set an Konventionen. Es bleibt also die Hoffnung, dass sich ein konsistenter Style durchsetzt und entsprechende Tools diesen in Visual Studio auch erzwingen können. Welchen Coding-Style verwenden Sie?

Share |

SharePoint 2010 Deployment (Teil 5) – Deployment von SQL Server Reporting Services Reports 16.05.2012

Thomas Nissen
Thomas Nissen, BI eXpert

Bei SharePoint 2010 Deployment handelt es sich um eine Serie von Blogbeiträgen, die die verschiedenen Schritte für das Deployment von SiteCollections erläutert und ausführt. Dabei wird der Fokus auf das Wiederherstellen einer Backup-Datei aus einer SharePoint-Umgebung in eine andere (z.B. von einer Entwicklungsumgebung auf eine Test- oder Produktivumgebung) gelegt.

 

Die Serie “SharePoint 2010” besteht aus den folgenden Beiträgen:

  1. Erstellen einer SiteCollection
  2. Wiederherstellen einer Backup-Datei
  3. Erstellen von Secure Store Applikationen
  4. Importieren von Business Data Connectivity Models (BDCM)
  5. Deployment von SQL Server Reporting Services (SSRS) Reports

Im letzten Teil dieser Blogserie behandeln wir das Deployment von SSRS Reports in eine SharePoint-Bibliothek mit Hilfe von Powershell. Dafür erweitern wir die, im ersten Teil der Blogserie, erstellte Konfigurationsdatei sowie das Powerhell-Skript, um die Aktionen für das Deployment von Report-Dateien (*.rdl) und der Datenquelldateien (*.rds). Dieses Powershell-Skript kann entsprechend der Anforderungen individuell angepasst werden.

Für die Erweiterung, der im ersten Teil der Blogserie erstellten Powershell- und Konfigurationsdatei, sowie das nachträgliche Ausführen sind die folgenden Schritte auszuführen:

  1. Erweitern der Konfigurationsdatei
  2. Erweitern der Powershell-Datei
  3. Ausführen

In diesem Beispiel deployen wir einen SSRS Report, der eine Shared DataSource verwendet.

Voraussetzungen

Voraussetzung, für ein erfolgreiches Deployment von SSRS Reports, ist das Bestehen einer Reportbibliothek, sowie einer Datenverbindungsbibliothek auf ihrer SiteCollection.

Des Weiteren muss SSRS im “SharePoint Integrated Mode” konfiguriert werden. Hier finden Sie eine Anleitung dazu.

Erweitern der Konfigurationsdatei

Um ein unabhängiges Deployment zu verwenden, haben wir im ersten Teil dieser Blogserie Erstellen einer SiteCollection eine Konfigurationsdatei (Konfiguration.xml) erstellt. Diese müssen wir um die zu konfigurierenden Elemente für das Deployment von Reports erweitern.

<ReportSourcePath>Pfad zu den Report-Dateien (*.rdl, *.rds, *.rsd)</ReportSourcePath>
<TargetReportLibrary>Name der Reportbibliothek</TargetReportLibrary>
<TargetReportFolder>Name des Ordners in dem die Reports liegen</TargetReportFolder>
<TargetDataConnectionLibrary>Name der Datenverbindungsbibliothek</TargetDataConnectionLibrary>
<TargetDataConnectionFolder>Name des Ordners in dem die DataSources liegen</TargetDataConnectionFolder>
<TargetDataSetLibrary>Name der DataSets-Bibliothek</TargetDataSetLibrary>
<TargetDataSetFolder>Name des Ordners in dem die DataSets liegen</TargetDataSetFolder>
<SSRSWebservice>/ReportServer/ReportService2010.asmx?wsdl</SSRSWebservice>

Erweitern der Powershell-Datei

Im folgenden Schritt erweitern das im ersten Teil der Blogserie erstellte Powershell-Skript (Deployment.ps1) um die in der Konfigurationsdatei neu erstellten Elemente.

#Reporting Variables
$ReportSourcePath = $configurationElement.Config_Content.ReportSourcePath
$TargRepLibrary = $configurationElement.Config_Content.TargetReportLibrary
$TargetReportFolder = $configurationElement.Config_Content.TargetReportFolder
$TargDataConnLibrary = $configurationElement.Config_Content.TargetDataConnectionLibrary
$TargDataConnFolder = $configurationElement.Config_Content.TargetDataConnectionFolder
$TargDataSetLibrary = $configurationElement.Config_Content.TargetDataSetLibrary
$TargDataSetFolder = $configurationElement.Config_Content.TargetDataSetFolder

$SSRSWebservice = $configurationElement.Config_Content.SSRSWebservice
$WebserviceUrl = $Webapplication + $SSRSWebservice

#Create Proxy
$ssrsProxy = New-WebServiceProxy -Uri $WebserviceUrl -UseDefaultCredential

$force = $true;
$ErrorActionPreference="Stop"
$proxyNamespace = $ssrsProxy.GetType().Namespace

Als nächstes erstellen wir drei Funktionen:

  1. Install-DataSource
  2. Install-DataSets
  3. Install-Reports

Install-DataSources

function Install-DataSource([string] $dsFile)
{
$dataSourcePath = $newCollectionSite + "/" + $TargDataConnLibrary
$dataSourceFolder = $dataSourcePath + "/" + $TargDataConnFolder

if($force)
{
#Check if folder is existing, create if not found
try
{
$ssrsProxy.CreateFolder($TargDataConnFolder, $dataSourcePath, $null)
Write-Host "Created new folder: $TargDataConnFolder"
}
catch [System.Web.Services.Protocols.SoapException]
  {
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $TargDataConnFolder already exists."
}
else
{
$msg = "Error creating folder: $TargDataConnFolder. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
}



try
{
#Load the data source Xml
[xml] $DSXml = Get-Content ($dsFile);
#Initialize a DataSourceDefinition object
$dsDefinition = New-Object ("$proxyNamespace.DataSourceDefinition");
#Initialize a DataSource object
$dSource = New-Object ("$proxyNamespace.DataSource")
$dSource.Item = $dsDefinition
#Read the settings from XML and populate related props
$dsDefinition.Extension = $DSXml.RptDataSource.ConnectionProperties.Extension
$dsDefinition.ConnectString = $DSXml.RptDataSource.ConnectionProperties.ConnectString
$dsDefinition.ImpersonateUserSpecified = $false
$dsDefinition.Prompt = $null
$dsDefinition.WindowsCredentials = $false
#$dsDefinition.CredentialRetrieval = [CredentialRetrievalEnum]::Integrated
$dSource.Name = $DSXml.RptDataSource.Name
$dsFileName = [String]::Concat($DSXml.RptDataSource.Name,".rsds")
$rsdsAbsoluteUrl = [string]::Concat($dataSourceFolder,$dsFileName)

#Call Proxy to upload report
$ssrsProxy.CreateDataSource($dsFileName,$dataSourceFolder,$force,$dsDefinition,$null)
if($warnings.Length -eq $null) { Write-Host "Upload Success." }
else { $warnings | % { Write-Warning "Warning: $_" }}
}
catch [System.IO.IOException]
{
$msg = "Error while reading rsds file : '{0}', Message: '{1}'" -f $dsFile, $_.Exception.Message
Write-Error $msg
}
catch [System.Web.Services.Protocols.SoapException]
{
$msg = "Error while uploading rsds file : '{0}', Message: '{1}'" -f $dsFile, $_.Exception.Detail.InnerText
Write-Error $msg
}
}

Install-DataSets

function Install-DataSets([string] $dSetFile)
{
$dataSetPath = $newCollectionSite + "/" + $TargDataSetLibrary
$dataSetFolder = $dataSetPath + "/" + $TargDataSetFolder

if($force)
{
#Check if folder is existing, create if not found
try
{
$ssrsProxy.CreateFolder($TargDataSetFolder, $dataSetPath, $null)
Write-Host "Created new folder: $TargDataSetFolder"
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $TargDataSetFolder already exists."
}
else
{
$msg = "Error creating folder: $TargDataSetFolder. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
}



#Load the data source Xml
[xml] $DSXml = Get-Content ($dSetFile);
$file = Get-ChildItem($dSetFile);
$site = Get-SPSite $newCollectionSite;
$web = $site.RootWeb;
$rsdFolder = $web.GetFolder("$TargDataSetLibrary/$TargDataSetFolder");

$destUrl = $dataSetFolder+"/"+$file.Name

(Get-Content $dSetFile) | % {$_ -replace "</DataSourceReference>",".rsds</DataSourceReference>"} | Set-Content -path $dSetFile

$rsdFile = $web.GetFile($desturl)
$fileCheckedOut = "N"
if($rsdFile.Exists)
{
$rsdFile.CheckOut();
$fileCheckedOut = "Y"
}
$stream = [IO.File]::OpenRead($dSetFile)
$resultingfile = $rsdFolder.files.Add($desturl,$stream,$true)
$stream.close()

if($fileCheckedOut -eq "Y")
{
$rsdFile.CheckIn("Deployment Script")
}
$rsdFile.Update()

WRITE-HOST "Successfully Deployed Data Set:" $file.Name
}


Install-Reports

function Install-Reports([string]$rdlFile)
{
$reportPath = $newCollectionSite+"/"+$TargRepLibrary

if($force)
{
#Check if folder is existing, create if not found
try
{
$ssrsProxy.CreateFolder($TargetReportFolder, $reportPath, $null)
Write-Host "Created new folder: $TargRepLibrary"
}
catch [System.Web.Services.Protocols.SoapException]
{
if ($_.Exception.Detail.InnerText -match "[^rsItemAlreadyExists400]")
{
Write-Host "Folder: $TargRepLibrary already exists."
}
else
{
$msg = "Error creating folder: $TargRepLibrary. Msg: '{0}'" -f $_.Exception.Detail.InnerText
Write-Error $msg
}
}
}

#Add datasource and dataset extensions
(Get-Content $rdlFile) | % {$_ -replace "</DataSourceReference>",".rsds</DataSourceReference>"} | Set-Content -path $rdlFile
(Get-Content $rdlFile) | % {$_ -replace "</SharedDataSetReference>",".rsd</SharedDataSetReference"} | Set-Content -path $rdlFile

#Get the RDL Item
$rptFileInfo = Get-Item ($rdlFile);
$reportName = $rptFileInfo.Name;

try
{
#Get Report content in bytes
$byteArray = gc $rdlFile -encoding byte

$reportFolder = $newCollectionSite + "/" + $TargRepLibrary + "/" + $TargetReportFolder

#Call Proxy to upload report
$warnings = $null
$ssrsProxy.CreateCatalogItem("Report",$reportName,$reportFolder,$force,$byteArray,$null,[ref]$warnings)
if($warnings.Length -eq $null) { Write-Host "Upload Success." }
}
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 while uploading rdl file : '{0}', Message: '{1}'" -f $rdlFile, $_.Exception.Detail.InnerText
Write-Error $msg
}

#Fix up the data source
[xml] $rptXml = Get-Content ($rdlFile);
#Extract the data source used by report
$localDSName = $rptXml.Report.DataSources.DataSource.Name
$dataSourceFolder = $newCollectionSite + "/" + $TargDataConnLibrary + "/" + $TargDataConnFolder

$rptAbsoluteUrl = [string]::Concat($reportFolder,"/",$reportName)
$rsdsAbsoluteUrl = [string]::Concat($dataSourceFolder,"/",$localDSName,".rsds")

$catalogItemDtSrcs = $ssrsProxy.GetItemDataSources($rptAbsoluteUrl)
$reference = New-Object ("$proxyNamespace.DataSourceReference")
$dsNew = New-Object ("$proxyNamespace.DataSource")
$reference.Reference = $rsdsAbsoluteUrl
$dsNew = $catalogItemDtSrcs[0]
$dsNew.Item = $reference
$ssrsProxy.SetItemDataSources($rptAbsoluteUrl,$catalogItemDtSrcs)
}

Im nächsten Schritt werden die Dateien (*.rdl, *.rsd, *.rds) in die entsprechenden Bibliotheken auf der Zielseite deployed.
$ReportDataSourcePath = $ReportSourcePath + "\DataSource"
$ReportDataSetSourcePath = $ReportSourcePath + "\DataSet"

#Deploy Data sources 
[Object[]] $dataSourcesToPublish = [System.IO.Directory]::GetFiles($ReportDataSourcePath, "*.rds");
$dataSourcesToPublish | % { Install-DataSource $_ };
  
#Deploy Data Sets 
[Object[]] $dataSetsToPublish = [System.IO.Directory]::GetFiles($ReportDataSetSourcePath, "*.rsd");
$dataSetsToPublish | % { Install-DataSet $_ };
  
#Deploy Reports
[Object[]] $reportsToPublish = [System.IO.Directory]::GetFiles($ReportSourcePath, "*.rdl");
$reportsToPublish | % { Install-Reports $_ };

Abschließend müssen die Dateien, falls noch nicht geschehen, als MajorVersion deployed. Ansonsten können andere Benutzer nicht auf die Reportdateien zugreifen und diese erfolgreich ausführen.

#Publish a Major Version of the files stored in the Report Library
$spAssignment = Start-SPAssignment
$list = (Get-SPWeb -identity $newCollectionSite -AssignmentCollection $spAssignment).Lists["$TargetReportLibrary"]
foreach($item in $list.Items)
{
   $item.File.Publish("Copy of version: $VersionLabel")
   $itemName = $item.Name
   Write-Host "Successfully Published Major Version for file: $itemName"
}

Ausführen

Öffnen Sie nun auf dem Server das Microsoft SharePoint 2010 Management Shell und navigieren zu dem Verzeichnis, in welchem Sie das Powershell-Skript Deployment.ps1 gespeichert haben.

Eine Anleitung, um diese Powershell-Skript auch mit dem vom Windows Server mitgelieferten Powershell-Tool ausführen zu können, finden Sie im zweiten Teil dieser Blogserie.

Den Code, der in den vorherigen Artikeln dieser Blogserie erstellt wurde, können Sie auskommentieren (<# Code #>). So wird nicht alles, sondern nur der Teil für das Deployment der SSRS Reports ausgeführt.

Führen Sie das Skript Deployment.ps1 aus. Anschließend sollte folgendes Fenster erscheinen.

image

Fazit

Zusammenfassend kann man sagen, dass sich mit Hilfe von Powershell in SharePoint 2010 einfach administrative und konfigurierbare Schritte ausführen lassen. Dieses bedeutet, insbesondere für Administratoren und Entwickler, eine erhebliche Zeitersparnis.

Das Deployen von SSRS Reports und deren dazugehörigen Datenquelldateien mit Hilfe von Powershell ist dahingehend sehr hilfreich, da die Pfade der SharePoint-Umgebung angepasst werden. Sollte man die Reports nicht mit diesem Skript deployen, bleiben die Pfade erhalten, die auf der SharePoint-Umgebung vorhanden waren von der die Backup-Datei erstellt wurde.

 

Mit den Dateien Konfiguration.xml und Deployment.ps1, die im Laufe der Blogserie erstellt und erweitert wurden, kann man ohne großen Aufwand seine entwickelte SiteCollection auf eine andere SharePoint-Umgebung übertragen. Man spart sich einen erheblichen Zeitaufwand, da Dateien (z.B. BDCM-Dateien) nicht noch der Umgebung angepasst werden müssen. Des Weiteren erspart man sich die gesamte Erstellung und Einstellung in der Central Administration der jeweiligen SharePoint-Umgebung.

Ich hoffe, dass ich Ihnen mit dieser BlogSerie das Deployment einer SiteCollection von der einen SharePoint-Umgebung zu einer anderen erleichtern konnte und wünsche Ihnen viel Spaß beim Testen des Deployments.

Die Dateien Deployment.ps1 und Konfiguration.xml zu der Blogserie SharePoint 2010 Deployment können Sie sich hier herunterladen.

Hier finden Sie eine Übersicht aller Blogbeiträge zu dieser Blogserie.

Teil 1                    Teil 2                    Teil 3                    Teil 4                    Teil 5

Share |