code it

Martins Tech Blog

Virtuelle Platten verkleinern

Bei der Arbeit mit Virtual Server mit dynamisch vergrößerbaren Platten fällt einem früher oder später auf, dass Microsoft die Bezeichnung "dynamisch vergrößerbar" sehr ernst genommen hat, denn die vhd-Dateien werden nach und nach immer größer und auch eine Defragmentierung auf dem Gast nach dem Löschen von Dateien kann daran nichts ändern.

Aber Hilfe ist in Sicht. Eine Verkleinerung ist möglich. Dazu geht man wie folgt vor... Ein Tipp noch: Da das Verfahren einige Minuten bis Stunden dauern kann und während dieser Zeit einiges an Systemressourcen benötigt, führt man es am besten während eines Zeitfensters durch, in dem man weder Gast noch Host anderweitig benötigt.

1.) Löschen nicht benötigter Dateien auf dem Gast

Damit das Verkleinern den größtmöglichen Effekt hat, sollte man auf dem Gast zunächst alle nicht mehr benötigten Dateien löschen. Beispiele dafür sind der Papierkorb, die Temp-Ordner oder Temporary Internet Files.

2.) Defragmentieren des Gasts

Nun defragmentiert man die Platte im Gast noch einmal, damit möglichst viel freier Platz zusammenhängt.

3.) Mappen der precompact.iso

In der Virtual Server Verwaltungswebsite muss man nun die Abbilddatei Precompact.iso mappen, damit man Zugriff auf den Pre-Compactor erhält.

4.) Ausführen des Pre-Compactors

Im Gast kann nun der Pre-Compactor gestartet werden. Damit wird die vhd-Datei für die Komprimierung vorbereitet.

5.) Herunterfahren des Gasts

6.) Ausführen der Komprimierung

In der Virtual Server Verwaltungswebsite wählt man nun unter Virtuelle Festplatten/Überprüfen die zu komprimierende vhd-Datei aus. Und hier findet man nun auch die Aktion "Virtuelle Festplatte komprimieren".

Soweit zur Theorie.... Jetzt muss ich nur noch jemanden finden, der das ausprobieren will, da ich keine mehreren Stunden Zeit hab.

Thumbnails erzeugen

Wer kennt sie nicht, die kleinen Vorschaubildchen, die man sich ansehen kann, um zu entscheiden, ob man das "richtige" Bild auch ansehen möchte? Heute stand ich vor der Aufgabe, genau diese Bildchen auch zu erstellen.

Nichts einfacher als das, dachte ich mir, denn bei anderen Projekten war ich auch schon über den Namespace System.Drawing gestolpert, der in dieser Beziehung sehr mächtig ist. Sehr überrascht stellte ich fest, dass hier die Entwickler des Framework wohl mitgedacht haben, denn Objekte des Typs Image haben von Haus aus eine Methode GetThumbnailImage, die genau das leisten soll.

Ich schaute mir also die MSDN-Seite zum Thema an und war doch etwas überrascht....

a) Die Methode hat den Parameter callback, der zwar nicht verwendet wird, aber trotzdem erzeugt und übergeben werden muss "In GDI+ ... wird der Delegat nicht verwendet. Sie müssen trotzdem einen Delegaten erstellen und einen Verweis auf diesen in diesem Parameter übergeben."

b) Die Methode hat den Parameter callbackData, der immer mit IntPtr.Zero übergeben werden muss.

"Interessante Methode!" sag ich dazu nur.

Noch überraschter war ich, folgendes Statement zu finden: "GetThumbnailImage funktioniert gut, wenn die angeforderte Miniaturansicht eine Größe von ca. 120 x 120 hat. Eine Anforderung einer großen Miniaturansicht (z. B. 300 x 300) eines Image-Objekts mit einer eingebetteten Miniaturansicht kann zu einem deutlichen Qualitätsverlust bei der Miniaturansicht führen. Es kann ggf. ratsam sein, das Hauptbild anstelle der eingebetteten Miniaturansicht zu skalieren, indem Sie DrawImage aufrufen."

Da ich nicht mit Sicherheit sagen kann, wie groß die Bilder irgendwann mal sein müssen und ich doch eine etwas generischere Methode brauche, entschließe ich mich also, das Bild wie genannt zu skalieren:

private Image GetThumbNailImage(Image BigImage, int ThumbNailWidth, int ThumbNailHeight)
{
// check params
if (BigImage == null)
{
  throw new ArgumentNullException("BigImage");
}

if (ThumbNailHeight <= 0)
{
  throw new ArgumentException("ThumbNailHeight");
}

if (ThumbNailWidth <= 0)
{
  throw new ArgumentException("ThumbNailWidth");
}

// create a new image with new size
Image thumbnail = new Bitmap(ThumbNailWidth, ThumbNailHeight);
// get a handle to the drawing interface for the new image 
Graphics thumbgraphics = Graphics.FromImage(thumbnail);
// draw the big image on the smaller image
thumbgraphics.DrawImage(BigImage, 0, 0, ThumbNailWidth, ThumbNailHeight);

return thumbnail;
}

Soweit, so gut... Nun hat man aber nicht immer die neuen Größen bei der Hand, sondern möchte auch mal nur die Breite oder die Höhe angeben und das Programm soll selbst den jeweiligen anderen Parameter errechnen und das Seitenverhältnis beibehalten. Also schreib ich mir noch schnell 2 Methoden, die diese Funktionalität bieten.

private Image GetThumbNailImageFixedWidth(Image BigImage, int ThumbNailWidth)
{
// check params
if (BigImage == null)
{
   throw new ArgumentNullException("BigImage");
}
if (ThumbNailWidth <= 0)
{
  throw new ArgumentException("ThumbNailWidth");
}

double ratio = (double) BigImage.Width / (double) BigImage.Height;

int newwidth = ThumbNailWidth;
int newheight = (int)(newwidth / ratio);

return GetThumbNailImage(BigImage, newwidth, newheight);
}
private Image GetThumbNailImageFixedHeight(Image BigImage, int ThumbNailHeight)
{
// check params
if (BigImage == null)
{
  throw new ArgumentNullException("BigImage");
}
if (ThumbNailHeight <= 0)
{
  throw new ArgumentException("ThumbNailHeight");
}

double ratio = (double)BigImage.Width / (double)BigImage.Height;

int newheight = ThumbNailHeight;
int newwidth = (int)(newheight * ratio);

return GetThumbNailImage(BigImage, newwidth, newheight);
}

Das war's auch schon. Für den Test nutz ich dann mal die Beispielbilder....

// Originalbild
Image bigimage = Image
.FromFile("C:\\Users\\Public\\Pictures\\Sample Pictures\\Desert Landscape.jpg"); // neues Bild mit vorgegebener Höhe Image smallimagebyfixedheight = GetThumbNailImageFixedHeight(bigimage, 200); // neues Bild mit vorgegebener Breite Image smallimagebyfixedwidth = GetThumbNailImageFixedWidth(bigimage, 200); // Thumbnails speichern smallimagebyfixedheight.Save("C:\\testimageh.jpg", ImageFormat.Jpeg); smallimagebyfixedwidth.Save("C:\\testimagew.jpg", ImageFormat.Jpeg);

That's it !

Anzahl von Vorkommen eines Textes in einem anderen Text ermitteln

Heute war ich auf der Suche nach einer Funktion, die die Anzahl der Vorkommen eines Textes in einem anderen Text ermittelt. Leider boten mir weder System.Text noch System.String eine Funktion, mit der das mit wenig Aufwand möglich war.

Die doch recht einfache Lösung des Problems fand ich mit den Regulären Ausdrücken (System.Text.RegularExpressions)

using System.Text.RegularExpressions;

private int CountStrings(string str, string regexStr)
{
 Regex regex = new Regex(regexStr);
 return regex.Matches(str).Count;
}

Speichern und Lesen von Binärobjekten in MS SQL Server 2005

Hin und wieder kommt es vor, dass man Dateien im BLOB-Format in einer Tabelle ablegt. Wenn man nun "schnell" auf diese Daten zugreifen will, steht man vor einem Problem, denn der Standard-SQL-Befehlssatz bietet keine Möglichkeit, Spalten mit Binärdaten zu füllen bzw. diese Daten wieder auszulesen und in eine Datei zu speichern.

Nun hat man nicht immer eine Entwicklungsumgebung zu Hand, um ein Programm zu schreiben, das per ADO oder ADO.NET diese Spalten befüllt. Auch der SQL-Server selbst bietet Möglichkeiten, um hier z.B. mittels Management-Studio tätig zu werden. Im Folgenden beschreibt ein Beispiel das generelle Vorgehen:

Als vorbereitende Maßnahme legt man eine Datenbank namens [pictures] an, die eine Tabelle mit dem Namen [images] enthält. Diese Tabelle enthält einfachheitshalber nur einen Primärschlüssel und das eigentliche Datenfeld. Wichtig hierbei ist, dass das Datenfeld den Datentyp [varbinary](max) hat.

CREATE DATABASE pictures
GO
USE pictures
GO 
CREATE TABLE [dbo].[images](
[imageid] [int] IDENTITY(1,1) NOT NULL,
[imageblob] [varbinary](max) NOT NULL
PRIMARY KEY CLUSTERED ([imageid]))
GO
 
Nun wird die Tabelle mittels INSERT befüllt. Mittels OPENROWSET in Kombination mit der Option BULK ist es möglich, Spalteninhalte als Binärstrom aus einer Datei (im Beispiel: bild1.jpg) zu lesen.
 
INSERT [dbo].[images]([imageblob])
SELECT BulkColumn
FROM OPENROWSET( BULK 'C:\bild1.jpg',
SINGLE_BLOB) as ExternalFile
GO
 
Führt man nun einen SELECT auf die Tabelle aus, sieht man, dass die Binärdaten gespeichert wurden.
 

Der umgekehrte Weg ist leider nicht ganz so einfach. Hierzu muss man das Programm bcp bemühen. Um bcp per SQL aufzurufen, muss der Server konfiguriert werden, dass Shell-Aufrufe möglich sind. Auch das ist (notwendige Berechtigungen vorausgesetzt) per SQL-Statement möglich:

EXECUTE sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'xp_cmdshell', '1'
RECONFIGURE WITH OVERRIDE
GO
EXECUTE sp_configure 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO
 
Für den eigentlichen Export ist noch eine Konfigurationsdatei erforderlich. Diese beinhaltet die Formatbeschreibung. Wird diese nicht verwendet hat die resultierende Datei zwar die passende Größe, kann aber nicht gelesen werden, da bcp beim Export einige automatische Zeichenersetzungen vornimmt. Die Formatdatei kann auf Kommandozeilenebene mit bcp erstellt und dann angepasst oder direkt mittels Editor erstellt werden.

Die resultierende Datei sollte final wie dargestellt aussehen:

Nun kann mittels eines BCP-Befehls eine Datei erstellt werden. Wichtig hierbei ist, dass immer nur 1 Spalte und eine Zeile ausgegeben wird, da a) nur eine Datei geschrieben wird und b) dies auch in der eben erstellten Formatdatei so beschrieben ist. exec master..xp_cmdshell 'bcp "select [imageblob] from [pictures].[dbo].[images] where imageid = 1" queryout C:\kopiebild1.jpg -T -S . -f c:\imageblob.fmt'

Die Datei liegt nun als kopiebild1.jpg wieder vor.

Hilfe bei Regulären Ausdrücken

Reguläre Ausdrücke sind bekanntlich ein weites Feld und nicht immer einfach. Bei meiner Suche bin ich auf die Seite Regular Expression Library (RegExLib) getroffen. Hier stehen für viele Anwendungsgebiete reguläre Ausdrücke zur Verfügung - meist mit einer Angabe der Fälle, die abgedeckt sind und die nicht abgedeckt sind. Zusätzlich findet sich hier auch ein Tool, mit dem reguläre Ausdrücke getestet werden können.

Assembly aus GAC wiederherstellen

Im Leben eines Microsoft-Entwicklers kommt es hin und wieder vor, dass man DLLs aus dem GAC wiederherstellen muss. Das Standardverhalten in der Windows-GUI lässt aber nur ein komplettes Deinstallieren (=Entfernen) aus dem Global Assembly Cache zu und damit wäre die DLL für immer verloren.

Hier kommt nun die gute alte Eingabeaufforderung zum Zuge. Wenn man damit in den GAC navigiert, bekommt man eine andere Ansicht als in der Windows-Oberfläche. Das erklärt auch, weshalb der Explorer mit einem Hinzufügen mittels Strg+C und Strg+V etwas überfordert ist und das Ganze schlichtweg ignoriert.

Von hier aus kann man sich nun weiter in die Tiefen des GAC begeben. Normale MSIL-Assemblies findet man in GAC_MSIL. Hier ist für jede Assembly ein Ordner angelegt, der wiederum einen oder mehrere Ordner mit Versionskennzeichnungen. Hier findet man dann die installierte DLL und kann diese z.B. mit dem Copy-Befehl an eine andere Stelle kopieren.

Einen interessanten Nebeneffekt kann man nun erzielen, wenn man in der Eingabeaufforderung einen solchen Ordner geöffnet hat und dann über die Windows-Oberfläche die DLL aus dem GAC deinstalliert und wieder hinzufügen möchte. Dann schlägt das Hinzufügen mit der Meldung "Das Erstellen von <DLL> oder das Erstellen einer Schattenkopie ist nicht möglich, wenn diese Datei bereits vorhanden ist."

 

In diesem Fall kann man das Problem mit dem Schließen der Eingabeaufforderung beheben.

Zeilenumbuch im Text eines ASP Label

Bereits mehrfach hatte ich das das Problem, dass ich Labels erzeugen wollte in denen Zeilenumbrüche vorkommen. Leider wurden sämtliche Versuche \n oder die NewLine-Spezifikation aus Environment zu verwenden komplett ignoriert.

Heute bin ich dann über die Lösung des Problems gestoßen: Man muss einfach <br/> statt \n verwenden und schon geht es. Vermutlich wird durch \n einfach der HTML-Quelltext umgebrochen und das wird natürlich als Leerzeichen dargestellt.

Error loading workflow: Runtime capabilities are not available with this type

Als ich nach mehreren Tagen mal wieder einen Sharepoint-Workflow in die Design-Ansicht laden wollte, bekam ich diese Meldung angezeigt.

Der Text, der wie ein Link aussieht, half mir auch nicht wesentlich weiter, denn bei Klick passierte nichts. Auch erneutes Abrufen aus der Quellcodeverwaltung und eine Neukompilierung des Projekts brachte keine Abhilfe.

Schließlich brachte mich eine intensive Suche zu einem Blogpost, in dem genau das Problem beschrieben stand. Die Lösung: Die komplette Solution muss einmal fehlerfrei kompiliert werden, dann klappt auch wieder die Designansicht....