Archiv für den Monat September 2012

Windows Freigabe (Share) erstellen und Berechtigungen zuweisen


Diese Tage habe ich eine kurze Recherche durchgeführt, wie man unter .NET eine Freigabe mit bestimmten Berechtigungen erstellt und habe dann das folgende Beispiel erstellt. Ich habe das Beispiel nicht in einer Domain testen können, sondern nur mit einem lokalen Benutzerkonto.

1. Anlegen einer Windows Freigabe

//Anlegen einer Freigabe auf dem lokalen Rechner
//Als erstes ein Verzeichnis anlegen.
Directory.CreateDirectory(@"C:\TestingFolder");

ManagementClass management = new ManagementClass("Win32_Share");
ManagementBaseObject input = management.GetMethodParameters("Create");
ManagementBaseObject output;

//Die Einstellungen für die Freigabe vornehmen
input["Description"] = "My Shared Folder";
input["Name"] = "My Shared Folder";
input["Path"] = @"C:\TestingFolder";
input["Type"] = 0x0; 

//Weitere Freigabetypen
// DISK_DRIVE = 0x0
// PRINT_QUEUE = 0x1
// DEVICE = 0x2
// IPC = 0x3
// DISK_DRIVE_ADMIN = 0x80000000
// PRINT_QUEUE_ADMIN = 0x80000001
// DEVICE_ADMIN = 0x80000002
// IPC_ADMIN = 0x8000003

//Anlegen der Freigabe
output = management.InvokeMethod("Create", input, null);
if ((uint)(output.Properties["ReturnValue"].Value) != 0)
{
    throw new Exception("Unable To Share The Directory");
 }
 else  {
     Console.WriteLine("Directory Successfully Shared");
 }

2. Zuweisen von Freigabeberechtigungen zu unserer Freigabe

Achtung, beim Zuweisen der Freigabeberechtigungen, gehen alle “alten” Freigabeberechtigungen verloren.

//Berechtigungen für die eben angelegte Freigabe setzten
//Anlegen der Freigabe für einen domainaccount
//NTAccount account = new NTAccount("domainname", "username");
//Anlegen der Freigabe für einen lokalen account
NTAccount account = new NTAccount("squadwuschel");
SecurityIdentifier sid = (SecurityIdentifier)account.Translate(typeof(SecurityIdentifier));
byte[] sidArray = new byte[sid.BinaryLength];
sid.GetBinaryForm(sidArray, 0);

ManagementObject Trustee = new ManagementClass(new ManagementPath("Win32_Trustee"), null);
//Wenn das ganze für eine Domaine sein soll diese hier mit angeben
//Trustee["Domain"] = "domainname";
Trustee["Name"] = "squadwuschel";
Trustee["SID"] = sidArray;

ManagementObject AdminACE = new ManagementClass(new ManagementPath("Win32_Ace"), null);
AdminACE["AccessMask"] = 2032127;
AdminACE["AceFlags"] = 3;
AdminACE["AceType"] = 0;
AdminACE["Trustee"] = Trustee;

ManagementObject secDescriptor = new ManagementClass(new ManagementPath("Win32_SecurityDescriptor"), null);
secDescriptor["ControlFlags"] = 4; //SE_DACL_PRESENT
secDescriptor["DACL"] = new object[] { AdminACE };

//Den Pfad zum share angeben, den wir oben angelegt haben und den Namen des Shares angeben
ManagementObject share = new ManagementObject(@"\\squadwuschel-pc\root\cimv2:Win32_Share.Name='My Shared Folder'");
share.InvokeMethod("SetShareInfo", new object[] { Int32.MaxValue, "My Shared Folder", secDescriptor });

3. Quellen

http://howtoideas.net/how-to-create-a-shared-folder-using-c

http://blogs.msdn.com/b/helloworld/archive/2008/06/06/programmatically-configuring-permissions-on-a-share-in-c.aspx

Advertisements

Eigenes C# “Plugin” (BusinessLogicModule) für SQL Server Replikation erstellen


Ich hatte das “Vergnügen” eine SQL Server Replikation einzurichten und in meinem Fall auch noch die Möglichkeit ein passendes Plugin zu erstellen, für z.B. Replikationsfehler die der SQL Server nicht von selbst lösen kann. Im folgenden Beitrag gehe ich daher nicht auf das Anlegen und Erstellen einer Replikation ein, sondern wie erstelle ich ein Plugin für die SQL Server Replikation.

Problemstellung war bei mir eine Tabelle mit einem zusammengesetztem Primärschlüssel der sich aus zwei Spalten zusammensetzt. Hier war es möglich das der Replication Server und der Replication Client Datensätze erzeugten die den gleichen zusammengesetzten Primärschlüssel haben. Wie und warum soll hier an dieser Stelle keine Rolle spielen.

0. Vorgehensweise

Um ein eigenes Replikationsmodul erstellen zu können werden die Folgenden Schritte durchgeführt, die ich dann noch näher erläutere

  • Es existiert eine eingerichtete Replikation (Mergereplikation) und man hat Zugriff auf den Replikationsserver (SQL Server 2008R2)
  • Erstellen einer einfachen Klassenbibliothek in C# und .NET 2.0, die die Logik enthält was z.B. bei einem Replikationsfehler zwischen Publisher und Subscriber unternommen werden soll
  • Einbinden der erstellten DLL über ein SQL Script was auf dem SQL Server ausgeführt werden muss
  • Unser Replikationsmodul in der Replikation als Konfliktlöser einstellen
  • Testen unseres Replikationsmoduls

1. Voraussetzungen

Eine installierte SQL Server 2008R2 Standard Installation inkl. einer eingerichteten Merge Replikation und die Daten werden vom Server an die Clients gepusht. Eine Client Installation mit z.B. einer SQL Server 2008R2 Express Instanz als Replication Client. (habe ich bisher nur unter diesen Bedingungen testen können)

Name der Replikation auf dem Replication Server: testReplication

2. Visual Studio Projekt erstellen

Für ein SQL Server Replication Plugin, wird als Projekt nur eine einfache Klassenbibliothek für .NET 2.0 benötigt. Unserer Klassenbibliothek muss noch ein Verweis hinzugefügt werden auf “Microsoft.SqlServer.Replication.BusinessLogicSupport” damit wir auf die Replikation Einfluss nehmen können. Dafür wird unsere Klasse von “BusinessLogicModule” abgeleitet. Die passende DLL findet man unter

“C:\Program Files (x86)\Microsoft SQL Server\100\COM\Microsoft.SqlServer.Replication.BusinessLogicSupport.dll”

Das Projekt steht auch wie immer unter Codeplex zur Verfügung und kann dort heruntergeladen werden unter:

https://squadwuschel.codeplex.com/

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “SqlMyReplicationModule”

3. Implementieren der passenden Anwendungslogik

In unserer Hauptklasse leiten wir von “BusinessLogicModule” ab und binden alle Abhängigkeiten ein.

   public class MyReplicationModule : BusinessLogicModule
    {
        #region Properties
        /// <summary>
        /// Gibt an welche Änderungen von unserem Plugin verarbeitet werden sollen.
        /// In unserem Falle nur Fehler die beim Einfügen des Subscribers oder Publishers auftreten.
        /// </summary>
        public override ChangeStates HandledChangeStates
        {
            get { return ChangeStates.SubscriberInsertErrors
                | ChangeStates.PublisherInsertErrors; }
        }
...

Die Funktion “HandledChangeStates” gibt hier an für welche Fälle unser Modul überhaupt benutzt werden soll.

/// <summary>
/// Initialisieren, damit wir alle wichtigen Informationen erhalten um z.B. eine DB Verbindung öffnen zu können und das Problem zu lösen.
/// </summary>
public override void Initialize(string publisher, string subscriber, string distributor, string publisherDb, string subscriberDb, string articleName)
{
    SqlReplicationConnection = new SqlReplicationConnection(publisher, subscriber, distributor, publisherDb, subscriberDb, articleName);
}

Wenn man die Replikationsdaten ändern/löschen möchte ist es wichtig die “Initialize” Funktion zu überschreiben, denn diese Funktion stellt alle wichtigen Strings zur Verfügung, damit wir später eine Verbindung zur Datenbank aufbauen können. Die Klasse “SqlReplicationConnection” ist von mir und kapselt einfach nur die SQL Verbindung und stellt einfache Rückgabewerte wie eine DataTable für SQL Abfragen zur Verfügung.

/// <summary>
/// Da wir in den HandleChangeStates nur auf InsertError "lauschen" wird nur diese Funktion von uns überschrieben.
/// </summary>
/// <returns></returns>
public override ActionOnDataError InsertErrorHandler(SourceIdentifier insertSource, DataSet insertedDataSet, ref ErrorLogType errorLogType, ref string customErrorMessage, int errorCode, string errorMessage, ref int historyLogLevel, ref string historyLogMessage)
{
     //Wenn es sich um die jeweils passende Tabelle handelt, die jeweiligen Aktionen ausführen. Hier wird überprüft ob die Tabelle
     //bei der es zu einem InsertError gekommen ist "Kategorie" heißt, wenn ja kann für diese Tabelle dann der passende Code ausgeführt werden.
     if (SqlReplicationConnection.ArticleName.ToLower() == "Kategorie")
     {
          //Hier die Replikation manipulieren und wenn alles erfolgreich abgelaufen ist, das passende Return liefern.
          string kategorieId = insertedDataSet.Tables[0].Rows[0]["id"].ToString();
          string secondId = insertedDataSet.Tables[0].Rows[0]["secondId"].ToString();
           //Die Abfrage erstellen die auf beiden Systemen durchgeführt werden soll Publisher und Subscriber
           string sqlString = string.Format("Select * from {0} where id = {1} and secondId = {2}", SqlReplicationConnection.ArticleName, kategorieId, secondId);

           //Prüfen ob hier ein Datensatz gefunden werden kann.
           DataTable tablePublisher = SqlReplicationConnection.GetDataTable(sqlString, SqlReplicationConnection.RepType.Publisher);
            //... Weitere Logik z.B. Löschen von Datensätzen oder Manipulieren

            return ActionOnDataError.AcceptCustomErrorBehavior;
       }

      return base.InsertErrorHandler(insertSource, insertedDataSet, ref errorLogType, ref customErrorMessage, errorCode, errorMessage, ref historyLogLevel, ref historyLogMessage);
 }

Dann überschreiben wir den passenden Handler der für uns am besten geeignet ist. In unserem Fall handelt es sich hier um den “InsertErrorHandler”. Das übergebene Dataset “insertedDataSet” ist nur zum überprüfen, bei welchem Datensatz der Fehler aufgetreten ist. Dieses Dataset kann man nicht manipulieren. Wenn man die Daten in der Datenbank anpassen möchte, dann muss man die SQL Verbindung selbst aufbauen und die passenden Abfragen selbst ausführen (beliebig komplex, da man ja auf die gesamte Datenbank zugriff hat). In meinem Beispiel führe ich nur ein einfaches Select aus, anhand der ids die ich im Dataset finden konnte. Die Verbindung wird an Hand der Daten aufgebaut die wir in der “Initialize” Funktion übergeben bekommen haben.

string.Format("Data Source='{0}';Initial Catalog='{1}';Integrated Security=SSPI", Publisher, PublisherDb)

Für eine Verbindung benötigen wir im Connectionstring nur den Namen der DataSource und den Datenbanknamen. Als Security wird hier einfach “SSPI” übergeben, denn unser Replikationsdienst besitzt ja bereits alle Rechte um auf die DB zugreifen zu können. Daher ist hier kein Username und kein Passwort notwendig.

4. Unser Modul auf dem SQL Replikationsserver registrieren

Das Script wird nicht auf der Replikationsdatenbank ausgeführt, sondern muss auf der „distribution“ Datenbank unter „System Databases“ ausgeführt werden. Außerdem muss das Script für jede Tabelle ausgeführt werden, die unser Modul benutzten soll. Denn wir können in einer DLL die Probleme mehrerer Tabellen implementieren.

DECLARE @publication AS sysname;
DECLARE @article AS sysname;
DECLARE @friendlyname AS sysname;
DECLARE @assembly AS nvarchar(500);
DECLARE @class AS sysname;
SET @publication = N'testReplication';
SET @article = N'Kategorie';
SET @friendlyname = N'MyReplicationModuleForKategorie';
SET @assembly = N'C:\ReplicationModule\SqlMyReplicationModule.dll';
SET @class = N'SqlMyReplicationModule.MyReplicationModule';

exec sp_unregistercustomresolver @article_resolver = @friendlyname
exec sp_registercustomresolver @article_resolver = @friendlyname
    , @resolver_clsid = NULL
    , @is_dotnet_assembly = 'true'
    , @dotnet_assembly_name =  @assembly
    , @dotnet_class_name =  @class

5. Unser Modul auf dem Replikationssystem als Konfliktlöser einstellen

Nach dem wir unser Modul erfolgreich auf dem SQL Server registriert haben, müssen wir noch unser Modul als Konfliktlöser einbinden. Dafür müssen wir die Eigenschaften der eingerichteten Replikation öffnen.

image

Und die “Eigenschaften dieses Tabellenartikels festlegen”.

image

Wenn wir dann unter “Konfliktlöser” die “Benutzerdefinierten Konfliktlöser” auswählen, sehen wir auch unser Modul mit dem im SQL Script festgelegten Namen aufgelistet “MyReplicationModuleForKategorie”, das Modul einfach auswählen. Jetzt löst der SQL Server bestimmte Konflikte die er nicht selbst lösen kann für die Tabelle “Kategorie” über unseren selbst geschriebenen Konfliktlöser.

image

Zum Testen würde ich empfehlen wenn die Replikation so eingerichtet wurde, das diese nicht automatisch alle XX Sekunden ausgeführt wird, sondern das diese manuell ausgelöst wird, dann hat man eine bessere Kontrolle über die Datensätze mit denen man das ganze testen möchte.

6. Allgemeine Informationen

  • Als ich das ganze das erste mal eingerichtet habe, musste ich aufpassen, das ich auch ein .NET 2.0 Projekt erstellt habe da es sonst Probleme mit dem SQL Server 2008R2 gab
  • Ich hatte Verständnisprobleme wie man denn die Daten manipulieren sollte, ich dachte erst das man dies mit dem übergebenen Dataset erledigen kann und habe nicht an eine eigene Verbindung gedacht, da ich nicht wusste wo ich den Usernamen und das Passwort herbekomme, was aber letztendlich gar nicht benötigt wird.
  • Sollte ich hier eine falsche Herangehensweise an den Tag gelegt haben, dann bin ich natürlich für jeden Tipp dankbar. Denn ich fand es schwer hier an wirklich gute Informationen zu kommen. Da ich in der MSDN nur ein Beispiel finden konnte in dem man Daten aus dem Dataset in ein Logfile schreibt und nicht wie man die Daten manipulieren kann.
  • Ich hoffe aus meinem Beispiel wird klar wie man die Daten manipulieren kann, wenn nicht würde ich mich über Kommentare freuen und werde versuchen das ganze anzupassen.

7. Quellen

http://blogs.msdn.com/b/sql_pfe_blog/archive/2011/04/08/merge-replication-conflict-detection-vs-conflict-resolution-part-2-custom-business-logic-handler.aspx

http://technet.microsoft.com/en-us/library/microsoft.sqlserver.replication.businesslogicsupport.businesslogicmodule%28SQL.100%29.aspx