Manuel Kukla's Blog

IT, Finanzen, Aktien, Kuriositäten und alltägliches

IIS und Windows-Prozessaktivierungsdienst WAS lässt sich nicht starten

Neulich hatte ich folgendes Problem:

Auf einem Windows-7-Test-(Server-)Rechner ließ sich der IIS7 nicht mehr starten. Die Diagnosedaten waren für die Suchmaschine meines Vertrauens leider sehr dürftig:

  • Der IIS-Dienst ließ sich nicht starten, weil der davon abhängige Windows-Prozessaktivierungsdienst nicht gestartet werden konnte.
  • Der WAS-Dienst lieferte Fehler 5005 im Ereignisprotokoll mit EventData 911A0780
  • ProcessMonitor gefiltert auf svchost.exe lieferte einen Fehler 0xC0190005 (STATUS_RM_NOT_ACTIVE) beim Zugriff auf C:\inetpub\temp\apppools\*.tmp

Nachdem es nichts gebracht hat den Ordner zu bereinigen, war die Lösung dann doch recht trivial:

fsutil resource setautoreset true c:\

in einer Admin-cmd und ein anschließender Reboot, wodurch ein chkdsk für C: durchgeführt wurde. Danach funktionierte wieder alles.

Die Ursache war wohl ein Absturz des Hosts, der das Dateisystem beschädigt hat.



Gigabyte GA-Z170X-Gaming 7 und Dual Sapphire RX 470

Hallo,

kurze Notiz für alle Mitleidenden:

Wer in die Verlegenheit kommt das oben genannte Board mit 2 Stück Sapphire Radeon RX 470 betreiben zu wollen: Das wird mit dem Auslieferungs-BIOS in der Version 7 nicht funktionieren. Eine Karte funktioniert problemlos, aber sobald der Treiber der zweiten installiert wird, ist es finster.

Abhilfe schafft hier ein BIOS-Update. Ich habe gleich das neueste Beta-Bios F20d verwendet. Dieses wirkt auch übersichtlicher, als die Version 7.

Wer keinen USB-Stick bei der Hand hat, kann einen Teil seiner Festplatte (z.B.: 1 GB) mit FAT32 formatieren und diese beim Flashen als Zwischenspeicher verwenden.

PS: Wer im Handbuch nachliest wird auch feststellen, dass der unterste PCIE x16 (als x4)-Slot seine Funktion verliert, sofern eine M.2 SSD eingebaut wird.

Einige PInvoke-Signaturen für dnsapi.dll

Kürzlich hatte ich das Bedürfnis meinem VPN-Client die Registrierung im DNS-Server beizubringen. Nachdem ein ipconfig /registerdns nicht zum Ziel führte (funktioniert anscheinend nicht für VPN-Adapter, oder nicht übers VPN, oder nicht mit den DHCP-Addressen vom VPN-Server, oder nur für den ersten DNS-Server, oder wie auch immer...) musste eine andere Lösung her.

Die Aufgabenstellung lautet: Einen DNS-Eintrag am internen DNS-Server im Remote-Netzwerk anlegen bzw. aktualisieren.

Deshalb kam ich in Verlegenheit dies selbst zu implementieren. Bald bin ich über die DnsQuery-Funktion gestolpert, welche jedoch ziemlich bescheiden dokumentiert ist. Wie ich dazu kam? Auf Codeproject gab es ein Projekt, welches das ganze via Extended T-SQL-Procedure macht. (http://www.codeproject.com/Articles/5535/Dynamic-DNS-Web-Service) Tja, "... It's not rocket science ..." dachte ich mir, wenn das mit einer extended-SP hinhaut. Leider war die halt nativ und benötigt keine PInvokes...

Ich war also auf der Suche nach den korrekten Deklarationen für C# für folgende Funktionen:

DnsQuery (bzw. DnsQuery_W)
DnsModifyRecordsInSet (bzw. DnsModifyRecordsInSet_W) DnsAcquireContextHandle (bzw. DnsAcquireContextHandle_W) DnsReleaseContextHandle

Die Basis dafür war die pinvoke.net-Seite, welche jedoch wie schon des öfteren bemerkt bestensfalls 80% der Lösung liefert und man die restlichen 20% hart erfrickeln muss. (http://www.pinvoke.net/default.aspx/dnsapi.dnsquery)

Nachdem ich nun die richtige Lösung für (mein) Problem gefunden habe, möchte ich es gerne für andere (und natürlich auch für mich :-)) archivieren.

    [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
    public static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpstrName, //_In_        PCTSTR      lpstrName,
                                       DnsRecordTypes wType,                                      //_In_        WORD        wType,      //Enum
                                       DnsQueryOptions Options,                                   //_In_        DWORD       Options,    //[Flags] Enum
                                       IntPtr pExtra,                                             //_Inout_opt_ PVOID       pExtra,
                                       ref IntPtr ppQueryResultsSet,                              //_Out_opt_   PDNS_RECORD *ppQueryResultsSet,
                                       IntPtr pReserved);                                         //_Out_opt_   PVOID       *pReserved

    [DllImport("dnsapi.dll", EntryPoint = "DnsModifyRecordsInSet_W", CharSet = CharSet.Unicode, SetLastError = false, ExactSpelling = true)]     public static extern int DnsModifyRecordsInSet(IntPtr pAddRecords, IntPtr pDeleteRecords, int Options, IntPtr hContext, IntPtr pExtra, IntPtr pReserved);     [DllImport("dnsapi.dll", EntryPoint = "DnsAcquireContextHandle_W", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]     public static extern int DnsAcquireContextHandle(int credentialsFlags, [InMarshalAs(UnmanagedType.LPStruct)] DnsApi.SecWinNtAuthIdentity credentials, out IntPtr handle);     [DllImport("dnsapi.dll", EntryPoint = "DnsReleaseContextHandle", CharSet = CharSet.Unicode, SetLastError = false, ExactSpelling = true)]     public static extern void DnsReleaseContextHandle(IntPtr handle);

    [DllImport("dnsapi.dll", CharSet = CharSet.Auto, SetLastError = true)]     public static extern void DnsRecordListFree(IntPtr pRecordList, DNS_FREE_TYPE FreeType);

Die structs
DnsQueryOptions, DnsUpdateOptions, DnsRecordTypes
können von der pinvoke-Seite übernommen werden.

Wichtig ist hingegen die korrekte Deklaration von DNS_RECORD, welche wie folgt aussieht. Ganz wichtig ist hier Charset.Unicode, da sonst nur das erste Zeichen des Strings vorhanden ist, und ein Update des Eintrags nicht funktioniert.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct DNS_RECORD
    {
      public IntPtr pNext;
      public string pName;
      public short wType;
      public short wDataLength;
      public int flags;
      public int dwTtl;
      public int dwReserved;
      public DnsData DATA;
      public short wPreference;
      public short Pad;
      private int Pad1;
      private int Pad2;
      private int Pad3;
      private int Pad4;
      private int Pad5;
      private IntPtr Pad6;
      private IntPtr Pad7;
      private IntPtr Pad8;
    }
 Ebenfalls wichtig ist, dass der Record nicht bei Pad aufhört, sondern dahinter auch noch Pad1-Pad8 kommt. (Quelle: http://stackoverflow.com/questions/6662381/how-to-update-some-com-marshalling-code-to-work-on-a-64-bit-system)

Der Aufruf für DnsQuery funktioniert dann wie folgt. Ich verwende als 4. Parameter ein Array von IPs, welche die befragten DNS-Server darstellen. (Dies ist u.a. hier dokumentiert: https://support.microsoft.com/en-us/kb/831226, jedoch nicht im offiziellen Artikel zur Funktion: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682016%28v=vs.85%29.aspx)

        String domain = "www.mku.name";
        
//Anfrage an spezifischen DNS-Server (strDNSIP) richten uint address = BitConverter.ToUInt32(IPAddress.Parse(strDNSIP).GetAddressBytes(), 0);         uint[] ipArray = new uint[1];         ipArray.SetValue(address, 0);         DnsIntf.IP4_ARRAY dnsServerArray = new DnsIntf.IP4_ARRAY();         dnsServerArray.AddrCount = 1;         dnsServerArray.AddrArray = new uint[1];         dnsServerArray.AddrArray[0] = address;         IntPtr ptrDNSServer = Marshal.AllocHGlobal(Marshal.SizeOf(dnsServerArray));         Marshal.StructureToPtr(dnsServerArray, ptrDNSServer, false);         var result = DnsIntf.DnsQuery(ref domain, DnsIntf.DnsRecordTypes.DNS_TYPE_ALL, DnsIntf.DnsQueryOptions.DNS_QUERY_BYPASS_CACHE, ptrDNSServer, ref recordsArray, IntPtr.Zero);
Die Ergebnisse können wie folgt durchlaufen werden:
        DnsIntf.DNS_RECORD record = new DnsIntf.DNS_RECORD();
        var recordList = new List<string>();
        for (var recordPtr = recordsArray; !recordPtr.Equals(IntPtr.Zero); recordPtr = record.pNext)
        {
          record = (DnsIntf.DNS_RECORD)Marshal.PtrToStructure(recordPtr, typeof(DnsIntf.DNS_RECORD));
          if (record.wType == (int)DnsIntf.DnsRecordTypes.DNS_TYPE_A)
          {
            IPAddress addr = DnsIntf.ConvertUintToIpAddress(record.DATA.A.IpAddress);
            recordList.Add(addr.ToString());
          }
        }

Die IP zu uint-Konvertierung erfolgt mit dieser Funktion. Ich habe hier noch ein Array.Reverse eingebaut, weil es in meinem Fall genau verkehrt herum war. Bei Nicht-Funktionieren anpassen, ich hab nichts zum Gegentesten ;-)
 public static IPAddress ConvertUintToIpAddress(uint ipAddress)
    {
      // x86 is in little endian
      // Network byte order (what the IPAddress object requires) is big endian
      // Ex - 0x7F000001 is 127.0.0.1
      var addressBytes = new byte[4];
      addressBytes[0] = (byte)((ipAddress & 0xFF000000u) >> 24);
      addressBytes[1] = (byte)((ipAddress & 0x00FF0000u) >> 16);
      addressBytes[2] = (byte)((ipAddress & 0x0000FF00u) >> 8);
      addressBytes[3] = (byte)(ipAddress & 0x000000FFu);
 
      if (BitConverter.IsLittleEndian) // korrekt?
        Array.Reverse(addressBytes);
 
      return new IPAddress(addressBytes);
    }

Ein neuer A-Record mit TTL 10s kann (etwas russisch^^) wie folgt erzeugt werden:

        DnsIntf.DNS_RECORD recordNew = new DnsIntf.DNS_RECORD();
        recordNew.dwTtl = 10;
        recordNew.pName = domain;
        recordNew.DATA.A.IpAddress = BitConverter.ToUInt32(newIPaddr.GetAddressBytes(), 0);
        recordNew.wDataLength = 4;
        recordNew.wType = 1;
        recordNew.flags = 8201;

Zum Aktualisieren des Eintrags wird ein Domänen-User benötigt -> Es muss der richtige Kontext erzeugt werden:
IntPtr ptrContext = new IntPtr();
var resultContext = DnsIntf.DnsAcquireContextHandle(1 /*TRUE for UNICODE*/new DnsApi.SecWinNtAuthIdentity(strUser, strDomain, strPwd), out ptrContext);
SecWinNtAuthIdentity ist wie folgt definiert. Quelle: https://searchcode.com/codesearch/view/12799292/

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public class SecWinNtAuthIdentity
    {
      public string User;
      public int UserLength;
 
      public string Domain;
      public int DomainLength;
 
      public string Password;
      public int PasswordLength;
 
      public uint Flags;
 
      public SecWinNtAuthIdentity(string user, string domain, string password)
      {
        User = user;
        UserLength = user != null ? user.Length : 0;
 
        Domain = domain;
        DomainLength = domain != null ? domain.Length : 0;
 
        Password = password;
        PasswordLength = password != null ? password.Length : 0;
 
        Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
      }
    }


Und dann noch zu guter Letzt das Aktualisieren des Eintrags. Das funktioniert so, dass eine (via pNext verkettete) Liste von Neuen sowie zu löschenden Einträgen übergeben werden muss. Im konkreten Fall ptrNew (welcher aus recordNew erzeugt wird), sowie recordsArray aus der oben durchgeführten Abfrage.

IntPtr ptrNew = Marshal.AllocHGlobal(Marshal.SizeOf(recordNew));
Marshal.StructureToPtr(recordNew, ptrNew, false);
 
IntPtr ptrOut = new IntPtr();
var result2 = DnsIntf.DnsModifyRecordsInSet(ptrNew, recordsArray, (int)DnsIntf.DnsUpdateOptions.DNS_UPDATE_SECURITY_USE_DEFAULT, ptrContext, ptrDNSServer, ptrOut);

Am Schluss sollte man noch etwas aufräumen:
 if (recordsArray != IntPtr.Zero)
 {
     DnsIntf.DnsRecordListFree(recordsArray, DnsIntf.DNS_FREE_TYPE.DnsFreeFlat);
 }
Vermutlich gibt es auch noch etwas anderes zu bereinigen, aber das hebe ich mir fürs Neue Jahr auf :-) Die Saubermacher unter den Lesern dürfen gerne in den Kommentaren posten.


Das Ende von Brokerjet...

Heute möchte ich mich mit einem Thema an euch wenden, welches mich persönlich sehr trifft. Am Donnerstag kam ein Mail eines Börsenverlags in meinen Posteingang, welches ich anfangs noch belächelte. Es hatte den Betreff "Wenn einem der Online-Broker spontan kündigt...". Ich dachte mir nichts dabei und schmunzelte ein bisschen darüber, bis ich las was tatsächlich dahinter steckte: Brokerjet stellt seine Dienste ein. Halt - Moment... Brokerjet? Das ist doch mein Broker! Nun ja, *Augen reib* das betraf mich doch tatsächlich selbst.

Ich war so ziemlich baff. Ich musste doch tatsächlich in einem Börsenbrief lesen, dass mir mein Broker gekündigt hatte. Keine Mail, kein Brief - nein - ausgerechnet durch einen Börsenbrief! Einen Tag später kam dann doch noch ein Brief, welcher mich mit "Lieber Herr Kukla," begrüßte. Sehr fein! Ein Kündigungsschreiben mit "Lieber Herr" zu beginnen grenzt doch schon an Verarschung, oder?

 

Die Auswege aus dem Schlamassel

Ausgerechnet einer (der in meinen Augen) besten Broker, welcher im Vorjahr mit der "Erste Group" fusionierte, setzt jetzt rund 37.000 Kunden vor eine ziemlich besch* Wahl.

  • Sie können entweder ihre Aktien einpacken, und irgendwo anders hin gehen. Brokerjet freut sich über Übertragsspesen / Lagerstellenwechselgebühr.
  • Sie können ihre Aktien verkaufen. Brokerjet freut sich über die Verkaufsgebühren.
  • Sie können mit dem Depot zur Erste wechseln und dort jährlich saftige Kontoführungsgebühren und Depotgebühren bezahlen.

Wie das dann mit dem automatischen KeSt-Verlustausgleich aussieht, steht aber in den Sternen.

Merkt ihr etwas? Brokerjet / bzw. die Erste Group gewinnt in allen Fällen.

Was kann man dagegen tun? Nunja - Versuchen einen Broker zu finden, der die Spesen übernimmt, und möglichst viel Negativwerbung machen.

 

Was ich bereits gemacht habe

*Mich auf Facebook beschwert

Dies hatte einen Anruf eines höheren Mitarbeiters des Kundenservice zur Folge. Außer viel Gerede, gab es jedoch keine konkreten Angebote oder Lösungsvorschläge. Es half mir jedoch das ganze ein bisschen besser zu durchblicken. Sie haben im Vorjahr die Fusion durchgeführt und müssen nun aus gesetzlichen Gründen auch die beiden Banksystem verschmelzen. Dies kostet jedoch Geld, was sie anscheinend nicht ausgeben möchten. Sie möchten dieses Kapital lieber in George stecken. (was mir nebenbei angemerkt überhaupt nicht gefällt, da dies jetzt schon eine zu große Datenkrake ist.)

Es war nun eine Managemententscheidung die Notbremse zu ziehen und die Entwicklung bzw. Brokerjet einzustellen. Aus meiner Sicht zahlt sich das vielleicht am Papier aus, es wird aber noch sehr viel Geld kosten, um das Kundenvertrauen wieder herzustellen. Ich wünsche ihnen jedenfalls einen orkanartigen Shitstorm :-)

Meine Frage bzgl. Sparplänen wurde auch negativ beantwortet. SDI (das Alternativsystem bei der Erste) bietet keine Sparpläne mehr, man kann nur mit dem Bankberater Investitionspläne für Erste-Fonds abschließen. Das nennt sich dann wohl Fondssparen?

*Meinen Bankberater bei der Sparkasse angerufen

Sehr nettes Gespräch - Er ist ebenfalls einer der 37.000 und versteht mich voll und ganz. Er hat auch schon den Unmut von anderen Kunden mitbekommen. Konkretes Angebot konnte er mir keines machen. Witzigerweise musste er mich dann auch noch zur Konkurrenz verweisen, da er selbst weiß, dass das neue System nur einen Bruchteil von Brokerjet kann. Stichwort Intradaytrading, Sparpläne, ...

*Versuchte Kontaktaufnahme mit dem Gewinnmagazin

Um sie zu fragen, ob sie nicht im nächsten Heft ein bisschen darüber berichten möchten.

*Versuchte Kontaktaufnahme mit dem VKI

Da ich es nicht ganz einsehe, warum ich bei einseitiger Vertragsbeendigung die Gebühren für den Lagerstellenwechsel auf meine Kappe nehmen soll.

 

*Was ich noch machen werde

Tja, das muss ich mir noch überlegen. Ich werde auf jeden Fall versuchen möglichst viel Staub aufzuwirbeln und versuchen zu verhindern, dass die Lemminge ins Verderben laufen.

Eventuell meine neue Rechtsschutzversicherung testen, wenn sich das mit der Wartefrist ausgeht.

To be continued...

 

Was mich an dem Thema besonders stört

  • Warum fusioniert man, und kündigt 1 Jahr später alle Kunden?
  • Warum macht man die Kündigung in der Sommerzeit wo halb Österreich auf Urlaub ist?
  • Warum setzt man die Frist so kurz an? Ich hätte deutlich besseres zu tun, als mich mit diesem Thema herumzuschlagen!
  • Warum wusste man es nicht vorher, dass man eine Systemumstellung braucht, die Geld kostet?
  • Wie kann man die neuen deutlich teureren Konditionen als Sonderangebot bezeichnen?
  • Warum vertröstet man Kunden darauf, dass George in 2-3 Jahren vielleicht wieder die selbe Funktionalität bietet? Ich möchte die Tankstelle sehen, die Ihren Kunden verklickert, dass es erst wieder in 2-3 Jahren Diesel gibt und in der Zwischenzeit Benzin getankt werden soll.
  • Wie naiv muss das Management sein, um zu glauben, dass das die günstigste Lösung war?
  • Mein Vertrauen zur "Erste" ist dadurch massiv ge/zer-stört worden.

 

Bitte teilen, damit möglichst viele der 37.000 in Österreich wachgerüttelt werden!

Edit: Ich habe eine Facebookseite erstellt - vielleicht mag sie ja der eine oder andere nutzen, um sich auszutauschen.

https://www.facebook.com/LebenNachBrokerjet

OneDrive for Business Part 2

Ratet mal, was auf meinem Win7-Rechner zu Hause nicht funktioniert? Nein, es ist kein Grafiktreiber der abgestürzt ist. Nein, es ist auch keine USB-Umschaltbox, die einen Wackelkontakt hat. Es ist wiedermal mein Lieblingsprogramm von Microsoft: Onedrive for Business

  

Nun, ich bin ja eher der Mensch, der die privaten IT-Probleme mit dem geringstmöglichen Aufwand zu lösen versucht. Unter anderem kommen da dann möglicherweise auch relativ kreative Lösungen zum Vorschein.

Wenn ich mir jetzt allerdings den Verlauf meines Onedrive-Problems ansehe, dann überlege ich mir manchmal, ob es nicht besser wäre den Vorschlaghammer auszupacken und es irgendwie mit erhöhtem Arbeitsaufwand "gscheit" zu lösen. Entweder mit einem Tool eines Dritterherstellers - oder die mir liebere Lösung: Ich mache es gleich selbst - wie andere bereits richtig meinten: Das kann ja keine Raketenwissenschaft sein ;-)

 

Ich muss natürlich noch die letzte Behauptung zurücknehmen, dass es auf meinem Netbook mit 8.1 / Office 2013 bisher keine Probleme gab. Die Synchronisation verweigerte auch dort plötzlich aus heiterem Himmel den Dienst. Eine Reparatur brachte nur bedingt etwas.

 

Anbei folgt nun eine Anleitung, die bisher noch alle "Onedrive for Business"-Installationen mit hängender Synchronisierung von Änderungen (zumindest für einige Zeit) wiederbelebte:

Disclaimer: Vorher eine Datensicherung machen!!!1einselfelf

Mittels Kontextmenü eine Reparatur versuchen. Abwarten, ob diese hilft.

Falls nicht:

taskkill /f /im groove.exe
taskkill /f /im msosync.exe
taskkill /f /im msouc.exe
taskkill /f /im winword.exe
taskkill /f /im excel.exe
taskkill /f /im powerpnt.exe
taskkill /f /im Onedrive.exe
taskkill /f /im Onedrive.exe
taskkill /f /im CSISYNCCLIENT.EXE
taskkill /f /im CSISYN~1.exe
cd %USERPROFILE%\AppData\Local\Microsoft\Office\15.0\
rmdir OfficeFileCache /s
cd %USERPROFILE%\AppData\Local\Microsoft\Office\
rmdir Spw /s

Nun müssen noch alle Explorer-Instanzen wegen etwaigen Dateivorschau-Fenstern geschlossen werden.

Falls dies auch nichts bringt: Rechner neu starten, und nochmals die Reparatur mittels Kontextmenü anstoßen.

 Ich wünsche euch allen noch ein fröhliches Synchronisieren!

Sharepoint 2013 mit Onedrive for Business - der Praxistest

Vor einiger Zeit habe ich mit dem Projekt "Wohnung" begonnen und hierfür ist es natürlich ideal, wenn man stets alle Daten bei sich hat. Also zum Beispiel am PC zu Hause die Angebote mittels Excel vergleichen, und das Dokument dann am nächsten Tag während dem Telefonat mit der Firma dann auch noch am Netbook öffnen können. Oder etwa gar in der Mittagspause am Büro-PC?

Hierfür gibt es von Microsoft eigentlich eine Lösung. Man nehme eine SharePoint (Foundation) 2013 und das Sync-Tool Onedrive for Business.

Zur Ausgangslage:

Heim-PC: Windows 7 und Office 2010
Netbook: Windows 8.1 und Office 2013
Büro-PC: Windows 7 und Office 2010

 

Installation des Clients:

Sofern man tatsächlich den richtigen Link findet, muss man dann rund 1-2 GB "streamen", um endlich seine Bibliothek synchronisieren zu dürfen.

Verwendet man einen eigenen SharePoint-Server, so muss man dessen URL in die richtige IE-Sicherheitszone eintragen. Andernfalls kann man sein Kennwort nicht speichern.

 

Fazit:

Dieses gefrickelte Stück Software hat weder Beta, noch Alpha-Status erreicht. Unter Windows 7 eine Katastrophe, unter Windows 8 habe ich komischerweise noch keine Probleme feststellen können. In der derzeitgen Version leider absolut _nicht_ (!) praxistauglich!

Schande über Microsoft, dass sie a) dieses Produkt in diesem Zustand herausbrachten - und b) keine Bugfixes dafür bereitstellen!

 

Sammlung der mysteriösen Fehler:

  • Doppelt hält besser 

zumindest bei den Verknüpfungen ^^

  • Systray

Mein Systray zeigt mir zu Hause gerade nur mehr OneDrive, jedoch kein Onedrive for Business. Die Synchronisation tut auch nicht mehr. Das Programm lässt sich über keine der beiden Verknüpfungen starten.

  • Systray #2

Mein Systray zeigt am Office-PC beide Icons an, Die Synchronisation funktioniert nicht mehr. Über die letzten Tage habe ich bereits 12 ausstehende Dateiänderungen angesammelt. 

 

  • Mysteriöse Explorer-Fenster

Von Zeit zu Zeit öffnet sich selbstständig der lokale Onedrive-Ordner auf meinem PC in einem neuen Explorer-Fenster. Noch dazu öffnet sich dann manchmal auch noch ein Dialog für die Kennwortabfrage für meinen SharePoint Server...

  • Inkompatible Office-Produkte gefunden

Monatlich nach dem Patchday verweigert der Client gänzlich den Dienst. Meine Vermutung: Die Updates kommen zeitnah via Streaming, erfordern aber die Updates für das restliche Office-Paket per Windows-Update. Nach Patchen und Neustart ist das Problem in den meisten Fällen erledigt gewesen.

  • Kritischer OneDrive for Business-Fehler

Trat auf meinem Windows 7 Heim-PC auf ... gelöst habe ich ihn noch nicht, aber er hat mich dazu gebracht diesen Post fertigzustellen.

Forefront Protection for Exchange 2010 (FPE 2010) build numbers

Aus gegebenem Anlass möchte ich hier eine Liste der Buildnummern von Forefront Protection for Exchange 2010 posten:

 

RTM 11.0.677.0
http://support.microsoft.com/kb/2181692 Rollup 1  11.0.689.0
http://support.microsoft.com/kb/2420647  Rollup 2  11.0.705.0
http://support.microsoft.com/kb/2538719   Rollup 3 11.0.713.0
http://support.microsoft.com/kb/2619883  Rollup 4 11.0.727.0
https://support.microsoft.com/en-us/kb/2919357

 Hotfix nach Rollup 4

(wg. möglichen Speicherproblemen)

 11.0.727.0 (!)

Insbesondere beim letzten Hotfix bitte aufpassen ... die Buildnummern sind identisch mit Rollup 4 und das Deployment muss mittels Copy&Paste und manuellem Dienstneustart erfolgen.

Windows Server 2012 R2 Netzwerkprofil ändern

Für mein letztes Projekt habe ich beschlossen einen neue Windows Server 2012R2 VM aufzusetzen. Da ich zu Hause (noch) keine Domäne habe stand ich vor dem Problem des "öffentlichen Netzwerkes".

 

Im Userinterface gibt es anscheinend keine Möglichkeit mehr dies zu ändern, jedoch habe ich einen simplen Powershell-Befehl gefunden:

 Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private

Sofern man mehrere Netzwerkadapter hat, einfach den Get-Befehl und die Pipe weglassen und den Set-Befehl um den Parameter -Name xxx erweitern:

Set-NetConnectionProfile -Name Netzwerk -NetworkCategory Private

  

 

Und plötzlich klappt es dann auch mit der Namensauflösung ;-)

Mein "Addon" für Brokerjet...

Ich bin seit längerem Kunde bei Brokerjet.at und verwende dort den "WebTrader" als Trading-Plattform. Diese ist eigentlich nicht schlecht, jedoch gibt es einen großen Minuspunkt: Man hat in der Depotansicht keinerlei Überblick, welche Aktie in der letzten Zeit gut gelaufen ist, und wo es gerade bröselt.

Nehmen wir hier als Beispiel eine meiner Lieblings-Depotleichen: Ein ETF auf den "ARCA GOLD BUGS"-Index, zu sehen in meiner Standard-Depotansicht: 

Hier sieht man, dass die Position derzeit etwa 13,8% im Minus liegt - schön und gut, wenn man weiß, dass sie auch schon Mal viel schlimmer dastand - das weiß ich jedoch nicht bei jeder Einzelnen meiner derzeit etwa 20 Positionen.

Ein aktuelles Chart erhält man via http://www.finanzen.net/etf/ComStage_NYSE_Arca_Gold_BUGS_UCITS_ETF welches dann folgendes Bild zutage bringt:

     

 

Man beachte, dass Finanzen.net den Preis in USD angibt. Aber auf boerse-frankfurt.de gibt es auch ein Chart in EUR.(http://www.boerse-frankfurt.de/de/etfs/comstage+nyse+arca+gold+bugs+ucits+etf+etf+LU0488317701)

Je nach Art und Weise der Teesud-Leserei und Skalierung, könnte man hier auch einen beginnenden Aufwärtstrend sehen - schön und gut - die Spitze im Februar 2015 inkl. einem möglichen Ausstiegszeitpunkt ist mir jedoch komplett entgangen.

 

Ich hoffe dass das Problem soweit erklärt ist - die Lösung beginnt mit einem kleinen "Wrapper" mit C# und dem Webbrowser-Control. Ich werde in den folgenden Absätzen die Lösung etwas erklären und die kleinen Herausforderungen darstellen. Source-Code werde ich vorerst keinen veröffentlichen - den gibt es möglicherweise nach individueller Anfrage bei mir :-)

 

Herausforderungen:

  • Automatischer Login im Depot - nur so können zuverlässig Kursdaten gesammelt werden.
  • Auslesen der Kursdaten aus dem HTML
  • iFrame-Konstrukte auf der Seite
  • Weiterleitungen während des Logins und auch später
  • Skriptfehler
  • Auswertung

 Automatischer Login im Depot

Finden der beiden Felder im Webbrowser für Username und Passwort, sowie Befüllen dieser mittels:

webBrowser.Document.GetElementById("Feldname").InnerText = "Wert";

Sofern die Felder befüllt sind, kann das Formular abgesendet werden. Dazu wird einfach das JavaScript ausgeführt:

object y = webBrowser.Document.InvokeScript("window.opener.refreshAfterPopupLogin()");

 

Auslesen der Kursdaten aus dem HTML

Sofern man über das Login zur richtigen Depotseite gekommen ist, geht's ans Auslesen der Daten. Hierfür empfiehlt sich ein Splitten der Tabellen nach Zeilen, und dann nach Spalten:

String[] sSplitLine = s.Split(new String[] { "<tr>""</tr>" }, StringSplitOptions.None);
String[] sSplitCols = sLine.Split(new String[] { "<td>""</td>" }, StringSplitOptions.None);

Anschließend müssen nur noch die minimalen HTML-Formatierungen entfernt werden, und die Daten lassen sich schon recht Schnell in eine Datenbank verpacken. Als Ausgangspunkt kann hier folgende DB-Struktur dienen:

CREATE TABLE [dbo].[PreisInfo](
  [PreisInfoId] [int] IDENTITY(1,1) NOT NULL,
   [Name] [nvarchar](100) NULL,
   [ISIN] [nvarchar](12) NULL,
   [Menge] [decimal](18, 3) NULL,
   [LetzterKurs] [decimal](18, 3) NULL,
   [DurchKaufpreis] [decimal](18, 3) NULL,
   [Marktwert] [decimal](18, 3) NULL,
   [PerformanceInkl] [decimal](18, 3) NULL,
   [PerformanceExkl] [decimal](18, 3) NULL,
   [Ertrag] [decimal](18, 3) NULL,
   [per] [datetime] NULL
) ON [PRIMARY] 

 iFrame-Konstrukte auf der Seite

 Nachdem ich die Daten-Auslesefunktion fertig hatte, und meine ersten Daten aus dem HTML-Source in die Datenbank übertragen hatte, kam ich noch auf ein recht nerviges Sicherheitsproblem: Auf der Seite werden iFrames mit verschiedenen Domains verwendet - hier schlägt der IE mit einer UnauthorizedAccessException zu, wenn man auf das Document zugreifen möchte. Die einfache Lösung ist die Umgehung der Frames, und Aufruf der direkten Unterseite. Wenn dies nicht funktioniert, kann man via http://stackoverflow.com/questions/10645143/webbrowsercontrol-unauthorizedaccessexception-when-accessing-property-of-a-fram einen CrossFrame-Zugriff versuchen - mir ist dieser jedoch nicht gelungen. Ich habe mich dann aus Zeitgründen für die einfachere Lösung entschieden.

Weiterleitungen während des Logins und auch später 

Leider besteht der Login bei Brokerjet aus mehreren Weiterleitungen - dies lässt sich am besten im DocumentCompleted-Event des Webbrowsers mit einem if behandeln:

if (e.Url.ToString().EndsWith("MySite.html"))

Skriptfehler

 Der eingebettete IE (WebBrowser) wirft gerne Skriptfehler. Dies lässt sich mit folgender Zeile abschalten:

webBrowser.ScriptErrorsSuppressed = true;

Auswertung

Gibt es für einen Programmierer etwas schöneres als korrekt aufbereitete, rohe SQL-Daten? Ja, klar - eine bereits fertige Statistik. Aber wer traut denn schon fremden Statistiken? ^^

Also zurück zum Start, ein bisschen Datum-Frickeln und ein paar Joins später kommt man auf folgendes Select:

 

DECLARE @VortagBeginn datetime
DECLARE @HeuteEnde datetime
SET @VortagBeginn = convert(nvarchar(20), dateadd(d, -1, getdate()), 104) + ' 00:00:00'
SET @HeuteEnde = convert(nvarchar(20), getdate(), 104) + ' 23:59:59'
SELECT @VortagBeginn as Von, @HeuteEnde as Bis
 
SELECT x.ISIN, PMin.Name, 
       PMin.LetzterKurs Kurs1,
       PMin.Marktwert Wert1,
       PMin.per Datum1, 
       PMax.LetzterKurs Kurs2,
       PMax.Marktwert Wert2,
       PMax.per Datum2,
       PMax.Marktwert - PMin.Marktwert as Diff,
       cast(ROUND((PMax.LetzterKurs-PMin.LetzterKurs) / PMin.LetzterKurs * 100, 2) as decimal(18,2)) as Veraenderung
			FROM 
			(
				SELECT ISIN, Min(PreisInfoId) as MinPreisInfoId, Max(PreisInfoId) as MaxPreisInfoId
				FROM PreisInfo
				WHERE per BETWEEN @VortagBeginn AND @HeuteEnde
				GROUP BY ISIN
			) x
			INNER JOIN PreisInfo PMin ON PMin.PreisInfoId = x.MinPreisInfoId
			INNER JOIN PreisInfo PMax ON PMax.PreisInfoId = x.MaxPreisInfoId        
			

Was nun folgendes Ergebnis liefert:

 

-> Seit gestern etwa 3,2% und 170€ Gewinn - sehr schön. Kluge Leute bemerken sicher, dass ich bei diesem Screenshot zu wenig entfernt habe. Aber wer sich die Arbeit macht, dem sei die Information gegönnt.

In diesem Sinne wünsche ich euch noch ein schönes Wochenende. Fehlersuche und Feedback ist natürlich gerne erwünscht.

 

Die Kommentarfunktion funktioniert wieder...

Dank eines aufmerksamen Benutzers habe ich heute bemerkt, dass die Kommentarfunktion nicht mehr funktioniert. Dies soll gleich Anlass zu einem neuen Blogpost werden.

Die Fehlermeldung war leider mehr als dürftig: 

Sorry, the following error occured while processing your comment: Fehler beim Rückruf

 

Rückruf? Wtf ^^ Na toll, das kann ja lustig werden. Eine Suche im Ereignisprotokoll am Server zeigte lediglich Informationen und harmlose Warnmeldungen. Das Logfile vom Blog unter App_Data\logger.txt zeigte Daten vom 24.11.2014.

Na gut - Fiddler auspacken und einmal schauen ob man dort etwas sieht. Wer es noch nicht kennt: ein ziemlich geniales Tool zum Debuggen von HTTP(s)-Requests. (http://www.telerik.com/fiddler)

 

Brachte aber auch nichts, hier steht zwar eine Nummer (1136), aber mit der bin ich auch nicht wirklich weitergekommen.

 

Durch Zufall bin ich dann auf einen Blogpost gestoßen, welcher mich zur Recaptcha-Extension brachte. Der Blogpost (http://www.surinderbhomra.com/Blog/Post/2011/06/21/BlogEngine-There-was-an-error-in-callback-issue) riet ReCaptcha zu deaktivieren. Mag vielleicht helfen, aber ist nicht optimal. Wie sich dann herausstellte lag es an den verwendeten Keys. Ich erstellte mir auf deren Homepage ein neues Schlüsselpaar. (http://www.google.com/recaptcha/intro/index.html - ahja, die wurden anscheinend bereits 2009 von Google gekauft^^)

 

Leider ließ sich das neue Schlüsselpaar im Einstellungsdialog nicht speichern. (Falls ihr jetzt genauso planlos wie ich gerade eben danach sucht: "Benutzerdefiniert" > Erweiterungen > ReCaptcha > auf den blauen Link klicken, um den Einstellungs-"Dialog" zu öffnen). Im Dialog erschien nach dem Speichern meine komplette Website in einem 100x200px-Fenster mit einer nichtssagenden Fehlermeldung.

 

Ein Bild sagt mehr als 1000 Worte - nämlich wiedermal nichts... (im Screenshot ist die komplette Meldung abgebildet, es gibt keine Details...)

 

Na dann... Sysinterals Process Monitor auspacken und schauen, was schief läuft. Ah, ja ein Zugriff verweigert für App_Data\datastore\extensions\MetaExtension.xml:

 Nach Korrektur der Berechtigungen für den entsprechenden Benutzer des IIS-Application-Pools war das auch wieder behoben und ich durfte meinen neuen Key speichern. Hinweis: Es betraf mehrere XML-Files in diesem Ordner - somit bekam der gesamte Ordner die passenden Berechtigungen.