Exil-Kieler (www.exil-kieler.net), Hobbyfotograf (www.winfoto.de), Podcaster, Gelegenheitsmusiker, Papa, Mensch.
...kleine Tipps am Rande.
Warum auch immer man eine Wordpress-Seite auf einem Windows-Server hosten sollte, lassen wir hier mal beiseite, Fakt ist:
Beim Update vom WP3 auf WP 3.1 kommt es bei auw Windows-Servern gehosteten Installationen zu einem "Infinite Loop", d.h. die Homepage gibt entweder einen Timeout zurück, oder Fehler 310 (net::ERR_TOO_MANY_REDIRECTS): Zu viele Umleitungen.
Solltet Ihr Wordpress also auf einem Windows-Server hosten, seid vielleicht noch zurückhaltend mit dem Upgrade.
Forumsthread hier:
[Update]
Das Netz ist schnell #schwarmintelligenz :
http://wordpress.org/extend/plugins/permalink-fix-disable-canonical-redirects-pack/
This does the trick!
Wenn die Ausnahmeregelungen für das anonyme Relaying regelmäßigen oder unregelmäßigen Veränderungen unterworfen sind und das dann auch noch auf mehreren Mailhubs aktuell gehalten werden muss, möchte man damit so wenig Aufwand wie möglich betreiben.
Ein Ansatz kann sein, eine zentrale Textdatei mit einer Liste von IP-Adressen zu pflegen und diese gescheduled per Powershell, bzw. Exchange Management Shell in die RemoteIPRanges des Receive-Connectors zu pumpen.
#Connector-Objekt holen
#erste IP setzen (=Range loeschen)
$out = Get-ReceiveConnector MyConnector
$out.RemoteIPRanges = "192.168.1.10"#Restliche IP’s aus der Datei zum Range hinzufügen
import-csv ausnahmen.txt | foreach {$out.RemoteIPRanges += $_.IPAddresses}#Werte im Connector setzen (Name nicht erforderlich, steht bereits im Objekt)
$out | Set-ReceiveConnector
Eine große Hilfe war das Posting von Michael B. Smith zu Multivalued Parameters in PowerShell.
Im letzten Posting ging es darum, in Aufgabenlisten die Auswahlmöglichkeiten in DropDownListen um einen oder mehrere Einträge zu ergänzen.
Was nun, wenn ein benötigtes Feld in den Listen, die in mehreren Projektsites verwendet werden und möglicherweise sogar in eine übergeordnete Ebene aggregiert werden, noch gar nicht vorhanden ist?
Wir brauchen also eine Funktion, die ein zusätzliches Feld in alle Tasklisten unserer Projektsites einfügt.
Ich stelle mir das so vor, dass ich meiner Funktion Namen, Typ und die SPFieldCollection der in der Liste enthaltenen Felder übergebe, die ich eventuell beim nötigen Durchiterieren ohnehin schon am Wickel habe:
AddField(string, SPFieldType, SPList.Fields)
public static SPField AddField(string ftitle, SPFieldType ftype, SPFieldCollection ffields)
{
//wenn das Feld noch nicht vorhanden ist...
if (!ffields.ContainsField(ftitle))
{
// ...füg's ein.
fields.Add(ftitle, ftype, false);
}
return fields.GetField(ftitle);
}
Simpel, nicht? Nun kann man schnell mal Felder unterschiedlichster Typen hinzufügen.
foreach (SPWeb myWeb in topWeb.Webs)
SPSite theSiteCollection = new SPSite(mySitecollection);
SPWeb topWeb = theSiteCollection.OpenWeb(mySite);
SPList myList;
{
myList = myWeb.Lists["Aufgaben"];
AddField("Besteller", SPFieldType.User, myList.Fields);
AddField("bestellt am", SPFieldType.DateTime, myList.Fields);
AddField("unterstützt durch", SPFieldType.User, myList.Fields);
}
Ich gehe hier natürlich davon aus, dass es in jeder Projektsite (myWeb) eine Aufgabenliste gibt. Eleganter wäre hier selbstverständlich, das vorher abzuprüfen, aber das schafft Ihr bestimmt selber. ;-) Und wie man bei Bedarf Einträge zu Auswahllisten hinzufügt, wisst Ihr ja auch schon ;-)
Angenommen, Ihr habt in Eurem SharePoint eine ganze Reihe - sagen wir - Projektsites, in denen immer die gleiche Aufgabenliste verwendet wird. Da kommt jemand daher und möchte in allen Aufgabenlisten aller Projektsites das Statusfeld vom Typ DropDownListBox um einen oder mehrere Einträge erweitern.
Mit wenigen Zeilen Code kann diese Ergänzung automatisiert werden. Hier mal wieder ein beispielhaftes CodeSnippet:
foreach (SPWeb myWeb in topWeb.Webs)
{
foreach (SPWeb subWeb in myWeb.Webs)
{
SPList subWebTaskList = null;
subWebTaskList = subWeb.Lists["Aufgaben"];
SPField myField = subWebTaskList.Fields["Status"];
SPFieldChoice fAuswahl = (SPFieldChoice)myField;
string[] lAuswahl = new string[2] { "Ersatzteil bestellt", "Ersatzteil geliefert" };
foreach (string auswahl in lAuswahl)
{
fAuswahl.Choices.Add(auswahl);
}
fAuswahl.Update();
}
}
Anforderung:
In einer Taskliste soll der Benutzer, dem die Aufgabe Zugewiesen wurde, durch weitere Mitarbeiter unterstützt werden. Diese sollen über diese "cc-Zuweisung" ebenfalls benachrichtigt werden, wenn die Aufgabe angelegt wird.
Nun, das ist mit Bordmitteln nicht allzu schwer zu lösen, stehen uns doch benutzerdefinierte Benachrichtigungen zur Verfügung, die sich mit gefilterten Ansichten steuern lassen. Benachrichtige also, wenn "Ein Element geändert wird, das in der folgenden Ansicht angezeigt wird". Wenn man dazu nun eine Ansicht bereitstellt, die nach [Ich] im zusätzlich in der Liste angelegten Feld vom Typ Person "unterstützt durch" filtert, hat man das als Admin, indem man den übrigen Benutzern diese Benachrichtigung "aufdrückt", die Sache schon gelöst. Fast. Denn das will man ja nicht immer wieder manuell ergänzen, wenn neue Benutzer hinzukommen. Ausserdem stört, mich zumindest, die automatische Benachrichtigung, die an die Benutzer versendet wird.
Also muss eine Automatisierung her.
SPAlert alert = mySite.Alerts.Add();
alert.User = user;
alert.Title = myList.ParentWeb.Title + " - " + myList.Title + ": CC-Zuweisung";
alert.AlertType = SPAlertType.List;
alert.List = myList;
alert.EventType = SPEventType.Add;
alert.AlertFrequency = SPAlertFrequency.Immediate;
alert.AlwaysNotify = true;
alert.Filter = "<Query><Eq><FieldRef Name=\"unterst_x00fc_tzt_x0020_durch\"/><Value type=\"string\">" + user.Name + "</Value></Eq></Query>";
alert.Update(false); //das false bewirkt, dass keine Mail an den Benutzer geschickt wird, dass eine Benachrichtigung für ihn eingerichtet wurde.
Und bevor Ihr jetzt auch wahnsinnig werdet: Achtet bei der Query darauf, dass selbst ein Leezeichen im Feldnamen ein "Sonderzeichen" ist:
So mit einer foreach-Schleife durch Collections zu iterieren ist ja ganz praktisch. Das hört aber meist in dem Moment auf, in dem man ein Element aus der Collection, durch die man sich gerade durcharbeitet, im gleichen Atemzug verändern oder gar löschen will. Dieses Ansinnen wird von .NET gnadenlos mit
Collection was modified; enumeration operation may not executequittiert.So zum Beispiel auch bei dem Versuch, alle Useralerts einer SharePoint Site zu eliminieren. Dies passiert, weil das foreach Statement einen Enumerator verwendet. Dieses kleine Objekt ist dazu gedacht, sich durch Collections zu bewegen, die sich nicht verändern.Ich habe mir in diesem Fall damit beholfen, die GUIDs der Elemente aus der durchiterierten SPAlertCollection in eine Arraylist zu schreiben und diese dann in einem zweiten Durchlauf zu löschen.foreach (SPWeb myWeb in mysite.Webs)
ArrayList deletelist = new ArrayList();
{
if (myWeb.Alerts.Count > 0)
{
SPAlertCollection theAlerts = myWeb.Alerts;
foreach (SPAlert eAlert in theAlerts)
{
if (eAlert.User != null)
{
deletelist.Add(eAlert.ID);
}
}
if (deletelist.Count > 0)
{
foreach (Guid deleteme in deletelist)
{
myWeb.Alerts.Delete(deleteme);
}
}
deletelist.Clear();
}
myWeb.Update();
}
Mit den Serienterminen in SharePoint Kalenderlisten ist das ja so eine Sache.
Iteriert man die Liste durch, um zum Beispiel einen heute Fälligen Termin zu finden, wird als Startdatum das des ersten Auftretens des Termins der Serie zurückgegeben, und das hilft z.B. für eine Terminerinnerung nicht weiter.
Mit einer handgeschnitzten Abfrage, die eine ItemCollection mit den aktuellen Items aus den wiederkehrenden Ereignissen füllt, lässt sich das Problem lösen. Wichtig dabei ist, mit der ExpandRecurrence-Property des SPQuery-Objekts die Serie aufzudröseln.
Das sieht dann so aus:
SPListItemCollection eventListItems;// sicherheitshalber prüfen, ob es sich auch um einen Kalender handelt.
SPSite theSiteCollection = new SPSite(mySitecollection);
SPWeb topWeb = theSiteCollection.OpenWeb(mySite);
myList = theSite.Lists[EventList];
if (myList.BaseTemplate == SPListTemplateType.Events)
{
SPQuery myRecurringEvents = new SPQuery();
myRecurringEvents.ExpandRecurrence = true;myRecurringEvents.Query = "<Where> <DateRangesOverlap> <FieldRef Name=\"EventDate\" /> <FieldRef Name=\"EndDate\" /> <FieldRef Name=\"RecurrenceID\" /> <Value Type=\"DateTime\"> <Now /> </Value> </DateRangesOverlap> </Where>";
eventListItems = myList.GetItems(myRecurringEvents);
}
//wenn's keine Kalenderliste ist, können wir uns die ListItems auch so holen...
else
{
eventListItems = myList.Items;
}//Das Ergebnis kann dann wieder durchiteriert werden
//mach' was draus}
foreach (SPListItem eventListItem in eventListItems)
{
Nachdem wir bereits das Recipient-Array in der Ausgabe des Messagetracking-Logs aufgelöst ausgegeben haben, wandeln wir das Ganze jetzt so um, dass jeder Recipient in einer eigenen Zeile in einem benutzerdefinierten Logfile auftaucht.
$sent=@(Get-MessageTrackingLog -server $MyServer -start $Start -end $End -EventID SEND -ResultSize unlimited |Where-Object{$_.Source -eq "SMTP"} | Select TimeStamp, MessageId, ServerIP, EventID, Source, Sender, @{Name="Recipients";Expression={$_.recipients}}, RecipientCount, totalbytes | Sort-Object -property timestamp )ForEach($item in $sent)
{
#Recipients aufdröseln
For ($i=0;$i -lt $item.RecipientCount;$i++)
{
if ($item.RecipientCount -eq 1)
{ $test = $item.Recipients }
else
{ $test = $item.Recipients[$i]}#Jeden Recipient in eigenem row-Datensatz (Hashtable) speichern
$row = @{} #Felder: TimeStamp, MessageId, ServerIP, EventID, Source, Sender, SingleRecipient, RecipientCount, totalbytes
$row.TimeStamp = $item.TimeStamp
$row.MessageID = $item.MessageId
$row.ServerIP = $item.ServerIP
$row.EventID = $item.EventID
$row.Source = $item.Source
$row.Sender = $item.Sender
$row.SingleRecipient = $test
$row.RecipientCount = $item.RecipientCount
$row.totalbytes = $item.totalbytes#Da die Ausgabe eines Arrays aus Hashtables in die Hose geht, wird hier die $row-Hashtable in ein PSObject umgewandelt
$myrowobj = New-Object PSObject
$myrowobj = ConvertTo-Object $row#Datensatzobjekt in Ergbnisliste schreiben
$ergebnis += $myrowobj
}
}#ergebnis in CSV-Datei ausgeben
$ergebnis | export-csv c:\ausgabe.csv
Um die Hashtable in ein Powershell-Objekt umzuwandeln, benötigen wir die folgende kleine Funktion:
function ConvertTo-Object($hashtable) #konvertiert Hashtable in PSObjekt
{
$object = New-Object PSObject
$hashtable.GetEnumerator() |
ForEach-Object
{ Add-Member -inputObject $object -memberType NoteProperty -name $_.Name -value $_.Value }
return $object
}
Das Ganze ist mit großer Wahrscheinlichkeit von hinten durch die Brust ins Auge geschossen und wer eine bessere Idee hat, darf sie gerne in die Comments schreiben, aber es funktioniert. :-)
Wenn jemand mal ein Exchange 2007 Messagetrackinglog mit der Powershell gefiltert in eine CSV-Datei ausgeben will und sich ärgert, dass die Recipients in ein System.String[] Array verpackt sind, dann kann man das wie folgt auflösen:
Get-Transportserver | Get-MessageTrackingLog -start $Start -end $End -EventID RECEIVE -ResultSize unlimited |Where-Object{$_.Source -eq "SMTP"} | Select TimeStamp, EventID, Source, @{Name="Recipients";Expression={$_.recipients}}, RecipientCount, totalbytes | Sort-Object -property timestamp | export-csv c:\myExport.csv