Fernsteuerung mit Outlook: Windows per E-Mail herunterfahren

Die Ausgangslage kann mal auf jeden von uns Windows-Benutzern zutreffen, man will oder muss seinen PC ausschalten, vergisst dies jedoch, was dann? Ein „Herunterfahren“ via Fernsteuerung kann per E-Mail über Outlook geschehen.

Dies wird über eine zuvor erstellte Datei gesteuert.

So wird es gemacht:

1. Ausschalt-Datei erstellen

Windows-Taste betätigen, den Editor starten und folgenden Text eingeben:

shutdown –s –f

Diese Datei wird gespeichert, allerdings nicht als „.txt“ sondern als „.bat“, z.B. „ausschalten.bat“.

2. Eine Outlook-Regel erstellen

In Outlook klicken Sie unter „Start“ auf Regeln -> Regeln und Benachrichtigungen verwalten ->
Neue Regel -> Regel ohne Vorlage erstellen: Regel auf von mir empfangene Nachrichten anwenden und Weiter.

clip_image002[1]

Um dafür zu sorgen, dass Outlook den PC ausschaltet, aktivieren Sie das Kontrollkästchen vor „die von einer Person/öffentlichen Gruppe kommt“ und „mit bestimmten Wörtern im Betreff“.

clip_image004[1]

Unter 2. Schritt klicken Sie auf den blau unterstrichenen Text „einer Person/öffentlichen Gruppe“, dort geben Sie Ihre E-Mail-Adresse ein und bestätigen mit OK. Danach klicken Sie auf „bestimmten Wörtern“, hier geben Sie z.B. ausschalten ein und klicken auf Hinzufügen, OK und Weiter.

Im nächsten Dialogfenster setzen Sie anschließend noch einen Haken neben
Anwendung starten“, klicken unten auf Anwendung und wählen die Datei aus Schritt 1 aus. Hierzu klicken Sie auf Ausführbare Dateien, Alle Dateien und doppelt auf ausschalten.

clip_image005[1]clip_image006[1]

Abschließend klicken Sie auf Fertigstellen und zweimal auf OK.

3. PC ausschalten

Nun testen Sie es, indem Sie sich selbst eine E-Mail schicken mit dem Betreff Ausschalten. Nach dem Abruf dieser E-Mail von Outlook fährt der PC herunter.

Hinweis: Vorsicht! Alle nicht gespeicherten Dateien gehen dabei verloren.

WDS und SCCM oder 2x WDS parallel betreiben / Probleme mit PXE lösen

Wenn man (z.B. während der Einführungsphase vom System Center Configuration Manager) den bisherigen WDS-Server (Windows Bereitstellungsdienste / Deployment Services) weiterhin nutzen will, aber parallel die Betriebssystembereitstellung (OSD) von SCCM benötigt, dann besteht im Wesentlichen das folgende Problem:

Da PXE auf Broadcasts basiert, kann es nur einen PXE-Server geben, den der Client letztlich kontaktiert (man kann per Verzögerung dafür sorgen, dass einer immer schneller ist als der andere). Wenn man nun also PXE am SCCM aktiviert, dann ist es quasi Glückssache, ob der Client zuerst die Meldung vom WDS oder zuerst die von SCCM empfängt – in den meisten Tests war SCCM schneller. Damit bleibt also nur eine der beiden Technologien nutzbar.

Aber es gibt eine Lösung! Diese ist leider a) nicht wirklich dokumentiert und b) seitens Microsoft auch nicht unterstützt (man hört aber, dass selbst Microsoft diese Lösung intern einsetzen soll).

Die Lösung besteht darin, dem Benutzer am Client die Wahl zu lassen, welchen der gefundenen PXE-Server er nutzen will. Um dies zu erreichen, ist am WDS-Server (also derjenige, der nicht der SCCM-Server ist) ein Registry-Key zu setzen:

pxe1

Zusätzlich muss am SCCM in den Eigenschaften des Distribution-Points (Verteilungspunkt) für eine ausreichende Verzögerung gesorgt werden (würde man zuerst den PXE vom SCCM booten, dann hat der RegKey dort keine Wirkung, da dieser nur auf den WDS-eigenen PXE-Provider wirkt, nicht aber auf den vom SCCM):

pxe5

Wenn nun ein Client einen PXE-Boot versucht (und die Verzögerung ausreichend war, dass sich zuerst der Nur-WDS-PXE-Server meldet), dann bekommt der Benutzer zusätzlich zu der Möglichkeit, per F12 vom Netzwerk zu booten, eine weitere Option: F11 für eine Server-Auswahl!

pxe2

Drückt man jetzt F12, wird wie gewohnt DIESER WDS-Server genutzt und von dort mittels PXE gebootet. Drückt man jedoch F11, werden zuerst alle verfügbaren WDS-Server erkannt:

pxe3

Danach bekommt man eine Auswahl-Liste mit allen gefundenen PXE-Servern:

pxe4

Hier kann nun der jeweilige PXE-Server gewählt werden. Der WDS-Server selber steht an erster Stelle, an zweiter Stelle steht hier der SCCM mit aktiviertem PXE.

Auf diese Weise ist es möglich, WDS und SCCM oder mehrere WDS-Server parallel zu betreiben. Natürlich muss die entsprechende DHCP-Infrastruktur aufgebaut sein, damit PXE überhaupt funktionieren kann!

IIS Express und FQDN

Mein aktueller Anwendungsfall einer ASP.NET Web-Anwendung benötigt statt dem üblichen localhost eine echte Domain als Namen. Wer aus Visual Studio eine Website startet, tut dies in der Regel mit IIS Express (früher Cassini Web Develeopment Server).

Dort wird ein zufälliger Port ausgewählt und in die universelle Konfiguration des IIS Express Web Servers in applicationhost.config eingetragen.

image

Den Speicherort der Datei findet man im Screenshot  des IIS Express (Task Bar – Notification Icons- Item- Rechtsklick). Um ein zweites Mapping einzutragen, wird diese Config Datei direkt per Notepad geöffnet. In den Bindings wird eine oder mehrere Bindungen aktiviert.

   1:    <bindings>
   2:  <binding protocol="http" bindingInformation="*:5376:localhost" />
   3:   <binding protocol="http" bindingInformation="*:80:ppedv.localtest.me" />
   4:  </bindings>

Die Adresse localtest.me zeigt immer auf die lokale localhost Adresse 127.0.0.1. und ist damit ein Trick eine echte Internet-Domain zu nutzen, ohne die Hosts-Datei oder schlimmeres zu beanspruchen. Port 80 ist nicht unbedingt erforderlich.

Wenn ein lokaler IIS installiert ist, fängt dieser per Universal Binding alle Domains auf der IP 127.0.0.1 ab. Auch ein Stoppen des www-Publishingdienst löst das Problem nicht. Erst wenn man einen Hostname im IIS Manager vorgibt, ist es anderen Websites möglich ein Binding durchzuführen.

image

Stolperstein #2 ist, dass Binden abseits von localhost auf IIS Express nur möglich ist, wenn IIS Express im Administrator Context läuft. Dazu muss Visual Studio als Administrator (rechte Maustaste runas Administrator) ausgeführt werden.

Im Ergebnis lauscht nun die Web App auf beiden URI

image

MVC 4 – Ein View für Create und Edit

Meist sind die Datenoperationen für Create und Edit gleich. Zumindest an der Oberfläche. Daher ist es naheliegend wenn man einen View für beide Operationen nutzen möchte. Das Visual Studio legt aber per Default immer zwei Views an.

Mit wenig Änderungen kann jedoch ein simpler Edit-View auch für die Datenanlage verwendet werden. Die Grundidee ist, dass es eine Eigenschaft im Datenobjekt gibt, die anzeigt, ob das Objekt neu ist, oder bereits vor dem Aufruf des Views bestand. In meinem BSP verwende ich den ID, der bei einem neuen Objekt auf –1 gesezt wird. Dafür habe ich eine “mini-Factory”-Klasse geschrieben:

    public static class DatenFactory
    {
        public static Daten GetDatenForID(int i)
        {
            Daten dat= new Daten()
            {
                ID =i,
                Name="Martin Groblschegg",
                Firma ="ppedv"
            };
            return dat;
        }

        public static Daten GetNewDaten()
        {
            Daten dat = new Daten()
            {
                ID = -1,
                Name = "",
                Firma = ""
            };
            return dat;
        }
    }

Das Datenobjekt selbst (die Klasse Daten) ist so simpel, dass ich sie hier nicht zeigen muss. Eine Klasse mit den Properties ID, Name und Firma.

DatenController

Nun zum DatenController. Dieser bekommt drei Actions: Edit, New und CreateOrEdit. CreateOrEdit ist die Action, die vom Formular aufgerufen wird.

Edit bekommt die ID des zu editierenden Objekts. Mit Hilfe der Factory wird das Objekt aus dem Store geholt und danach dem View mit dem Namen “EditOrCreate” übergeben:

        public ActionResult Edit(int id)
        {
            Daten daten = DatenFactory.GetDatenForID(id);
            return View("EditOrCreate",daten);
        }

 

Die Action New arbeitet ähnlich, jedoch wird keine ID übergeben und das Objekt neu gebildet und diese hat daher den ID –1:

        public ActionResult New()
        {
            Daten daten = DatenFactory.GetNewDaten();
            return View("EditOrCreate", daten);
        }

Die letzte Action ist die CreateOrEdit-Action, die vom Formular aufgerufen wird:

        [HttpPost]
        public ActionResult CreateOrEdit(Daten dat)
        {
            
            if(dat.ID <0)
            {
                // Neu in DB Anlegen
                DBHandling.NewDaten(dat);
            }
            else
            {
                // Update in Datenbank
                DBHandling.UpdateDaten(dat);
            }
            return RedirectToAction("Index", "Home");
        }

Create-Or-Edit-View

Als nächstes muss der View angelegt werden. Im Add View Dialog wird der ViewName auf “EditOrCreate” gesetzt. Als Template wird das “Edit” Template gewählt und die Model-Class ist die Daten-Klasse:

image

Im View ist die Zeile "@using (Html.BeginForm())” zu finden. Diese muss angepasst werden, sodass im Post die “CreateOrEdit” Action des Daten-Controllers aufgerufen wird. Die Zeile lautet also richtig:

@using (Html.BeginForm("CreateOrEdit", "Daten"))

 

Mit diesen Änderungen ist der Controller und View fertig. Um es zu testen, wird nach der Start-View angepasst, um die beiden Actions aufzurufen. New wird ohne Parameter aufgerufen und Edit erhält einen Parameter ID mit dem Wert 123.

<ul class="nav navbar-nav">
<li>@Html.ActionLink("Neu", "New", "Daten")</li>
<li>@Html.ActionLink("Edit", "Edit", "Daten", new {ID=123}, null)</li>
</ul>

 

Sourcecode

Den gesamten Code gibt es in meinem OneDrive zum Download. (Link zum Download). Die NuGet-Packs wurden entfernt und müssen neu geladen werden.

Facebook-Login in ASP.NET Webforms

Die Welt bleibt nicht stehen. Das Nutzungsverhalten von Websites ändert sich. Das durchaus bewährte ASP.NET Membership Provider System wird durch ASP.NET Identity abgelöst. Wer heute mit Visual Studio 2013 ein neues Web Projekt anlegt, findet die komplette Benutzerverwaltung voreingerichtet - basierend auf Microsoft.AspNet.Identity.

Das Modul wird in der Datei IdentityConfig aus dem Verzeichnis App_Start hochgefahren. Dort kann man auch die unsäglich strikte Passwort-Policy aufweichen.

   1:     manager.PasswordValidator = New PasswordValidator() With {
   2:            .RequiredLength = 1,
   3:            .RequireNonLetterOrDigit = False,
   4:            .RequireDigit = False,
   5:            .RequireLowercase = False,
   6:            .RequireUppercase = False
   7:          }

 

Die Struktur bzw. die Eigenschaften des Benutzers werden in der Klasse ApplicationUser der Datei IdentityModels vorgegeben. Entsprechend des Code First Paradigmas des Entity Frameworks und der Einstellung aus der Web.Config wird dann die Datenbank automatisch angelegt.

   1:  <connectionStrings>
   2:      <add name="DefaultConnection" 
connectionString="Data Source=(LocalDb)\v11.0;AttachDbFilename=
|DataDirectory|\aspnet-WebFormsIdentity-20140712075341.mdf;
Initial Catalog=aspnet-WebFormsIdentity-20140712075341;Integrated Security=True"
   3:        providerName="System.Data.SqlClient" />
   4:    </connectionStrings>

 

In der Datei StartUp.Auth.vb sind Methoden für Twitter, Google und Facebook vorkonfiguriert, aber auskommentiert.

   1:   app.UseFacebookAuthentication(
   2:   appId:=ConfigurationManager.AppSettings("FacebookId"),
   3:   appSecret:=ConfigurationManager.AppSettings("FacebookSecret"))

Die Authentication Methode erwartet zwei Parameter, die ID und das Secret. Anders als eine Benutzername-Passwort-Kombination ist diese sozusagen das Login einer Anwendung. Der Benutzer sieht diese Daten nicht und entsprechend macht es Sinn, diese in die Web.Config auch aus dem Code auszulagern.

Im nächsten Schritt muss der Entwickler eine App bei Facebook anlegen, um die Parameter ID und Secret zu generieren. Es wird also ein Facebook-Account benötigt. Einstiegspunkt ist das FB Developer Portal.

Nachdem die App per New App erstellt wurde, können die beiden Parameter aus dem Formular kopiert werden.

image

Wer jetzt loslegt, wird eine Fehlermeldung im Browser erhalten.

Die Anwendungseinstellungen lassen die angegebene URL nicht zu: Eine oder mehrere URLs sind in den Einstellungen der App nicht zugelassen. Sie müssen mit der Website-URL oder der Canvas-URL übereinstimmen, oder die Domain muss Subdomain einer der App-Domains sein.

Dazu muss man wissen, dass eine sogenannte 2-Legs-Authentifzierung auf Basis von OAuth2 zum Einsatz kommt. Im Kern übernimmt ein Provider – hier Facebook – die Anmeldung und reicht dann einen Zugangstoken an die aufrufende Website weiter. Dazu ist es aber nötig, Facebook die URL der aufrufenden Website mitzuteilen.

Die Einstellung wird im Developer Portal von Facebook in den Settings der App vorgenommen.

image

Die hier gewählte lokale URL ist natürlich nicht praxistauglich.

Der Login Dialog der ASP.NET Website erzeugt nun einen zusätzlichen Button für den Facebook-Login auf der rechten Seite.

image

Folgerichtig wird das Passwort in der Tabelle ASPNetUsers auch frei gelassen. Nur ein lokaler Benutzer kann auch ein Passwort besitzen, das per Hash verschlüsselt gespeichert wird.

image

Den zweitbesten per TSQL finden

Das passt ja fast zur Fussball-Weltmeisterschaft. Ein Teilnehmer einer ppedv Schulung schreibt mir:

“Ich habe da eine „knifflige“ Aufgabe in der Firma, wo ich mit meinem bescheidenen SQL-Wissen nach ein paar Stunden nicht mehr weiter komme. Ich kann's natürlich über Umwege lösen, glaube aber dass man das mit einem sql query oder sql query + stored procedure oder function auch zusammenbringt.”

Kurz zusammengefasst: eine SQL Abfrage im Microsoft SQL Server soll den zweiten Wert liefern. Die Frage war mir neu und mein SQL-Wissen auch ein wenig eingerostet - teilweise war es auf dem Stand von SQL 2000. Seit 2005 ist aber RowNum hinzugekommen und seit 2012 auch ein Offset-Kommando, um in Kombination mit z.B. TOP /Group By einfach einen Datensatz zu überspringen. So zunächst mein erster Gedanke.

Die Ausgangstabelle

   1:  CREATE TABLE [dbo].[KickiTest](
   2:      [ID] [int] IDENTITY(1,1) NOT NULL,
   3:      [Ergebnis] [bit] NULL,
   4:      [Datum] [datetime] NULL,
   5:      [Seriennummer] [int] NULL
   6:  ) ON [PRIMARY]

Die Daten

image

Abfrage: Für alle Geräte, deren Ergebnis positiv ist, der zweite Datensatz. Wenn dieser nicht vorhanden ist, wird der erste abgefragt. 

Die Lösung verwendet die generierte Zeilennummer in Kombination mit einem Zähler

   1:  WITH tmp  AS (SELECT ID, Ergebnis, Datum, Seriennummer,
   2:         anzahl= Count(seriennummer) OVER(PARTITION BY seriennummer) ,
   3:          ROW_NUMBER() OVER (PARTITION BY seriennummer 
ORDER BY seriennummer,datum) AS RowNum
   4:          FROM   KickiTest where ergebnis=1) 
   5:   
   6:   
   7:  SELECT ID, Ergebnis, Datum, Seriennummer,rownum,anzahl
   8:  from tmp
   9:  where (anzahl = 1) or (anzahl>1 and rownum=2) 

 

Windows 8.1 Update 2 und Windows 9 – erste Gerüchte

Aktuellen Gerüchten zufolge soll im August 2014 das Update 2 für Windows 8.1 kommen. Dieses soll auch Voraussetzung für ein späteres Upgrade auf Windows 9 sein, liefert wohl aber nicht das erhoffte Startmenü wieder zurück. Dieses wird wohl erst in Windows 9 enthalten sein, welches voraussichtlich im Herbst 2015 final erscheinen soll. Vermutlich wird dann also im Herbst 2014 eine erste Vorabversion (“Preview”) erhältlich sein.

Im neuen Windows 9 kehrt das Startmenü dann wie von vielen Kunden gewünscht zurück – allerdings nur auf Geräten ohne Touchscreen. Hier wird es auch nicht möglich sein, den Startscreen von Windows 8 zu nutzen. Dafür lässt sich das neue Startmenü dann wohl auch vergrößert darstellen und die Modern Style UI Apps laufen dann in der “Desktop-Welt”. Auf Touch-Geräten wird wohl weiterhin das Menü aus Windows 8 enthalten sein.

t1-f9a18ec5da6206e5

(Abb.: So könnte das neue (“alte”) Startmenü in Windows 9 auf Nicht-Touch-Geräten aussehen)

Zeitgleich mit dem Release von Windows 9 wird wohl auch Windows 365 starten – ein neues Modell, bei dem man das Betriebssystem nicht kauft, sondern in einem Abo mietet. Gerüchten zufolge soll Windows 9 für Besitzer von Windows 8.1 kostenlos sein, für den Rest schlägt der reguläre Kauf wohl mit einem Preis von etwa 80€ zu Buche. Hier muss man sehen, was das Abo-Modell im Vergleich kosten wird.

Windows Server 2012 R2: Datei nach Dedeplizierung nur noch 0 Byte groß

In einem früheren Artikel habe ich beschrieben, wie man auf einem Windows Server 2012 die Datendeduplizierung (Data-Deduplication) konfiguriert und nutzt. Dort war in einem einfachen Beispiel zu sehen, dass die Datei nach der Deduplizierung noch genau 4KB belegt hat, also die verwendete Blockgröße (“Größe der Zuordnungseinheit”).

dedup_2012r2_1

Das liegt daran, weil die nach der Deduplizierung verwendeten Chunks nicht mehr beim betroffenen File liegen. Die Datei ist also tatsächlich 0 Byte groß – belegt aber eigentlich an einer anderen Stelle Speicherplatz.

Mit Hilfe des PowerShell-Cmdlets “Measure-DedupFileMetadata” lässt sich die tatsächlich belegte Speichermenge ermitteln:

dedup_2012r2_2

Hier belegt die SizeOnDisk nur noch die zu erwartenden 4KB…

Die eigentlichen Chunks liegen im Ordner “System Volume Information”, an dessen Inhalt man aber nicht ohne Weiteres herankommt.

dedup_2012r2_3

InfoPath – Daten per Webservice GetUserProfileByName aus AD abrufen

Benutzer von InfoPath haben häufig den Wunsch Formulare so gut wie möglich zu automatisieren. Beispielsweise so, dass die Benutzerdaten beim Formularaufruf automatisch in die vorgesehenen Felder eingefügt werden. Hierfür wird der sog. Webservice call to GetUserProfileByName verwendet. In dem folgenden Artikel wird die Vorgehensweise Schritt für Schritt erläutert.

Die Schritte:

· Datenverbindung erstellen

clip_image002

· Über das Register Daten -> Datenverbindungen wird zuerst eine Datenverbindung erstellt.

clip_image004

· Neue Verbindung (Daten empfangen)

clip_image006

· SOAP-Webdienst auswählen

clip_image007

· Speicherort: URL der Websitesammlung “/_vti_bin/UserProfileService.asmx?WSDL”, siehe Abbildung.

clip_image009

· Auswahl der Methode: GetUserProfileByName

clip_image011

· Klick auf „Weiter“

clip_image012

· Klick auf „Weiter“

clip_image013

· Aktivieren des Kontrollkästchens „Kopie der Daten in der Formularvorlage speichern“ lässt das Arbeiten im Offlinemodus, wie oben beschrieben, zu.

clip_image014

· Namen für die Datenverbindung -> Fertig stellen

Die Datenverbindung steht. Das Formular wird erstellt, in diesem befinden sich die entsprechenden Felder, in die Daten des Mitarbeiters eingelesen werden, der das Formular öffnet. In diesem Beispiel zeige ich das anhand des Benutzernamens. Weitere Daten werden analog dazu auf die gleiche Art und Weise abgerufen.

In meinem Beispiel habe ich ein Textfeld mit der Bezeichnung „Anforderer“ verwendet.

· Um es aufzurufen, gilt es über die Steuerungstools die Steuerelementeigenschaften zu benutzen.

clip_image016

· Feld markieren, Register Eigenschaften -> Steuerelementeigenschaften

clip_image017

· Das Feld „Wert“ wird mit Klick auf die Schaltfläche „Fx“ bearbeitet.

clip_image019

· Schaltfläche „Feld oder Gruppe einfügen…“

clip_image021

· Das Feld „Felder“ wird aufgeklappt und hier die (Sekundär-) Datenquelle ausgewählt.

clip_image022

· Der „Value“ muss ausgewählt werden und Klick auf die Schaltfläche „Daten filtern“

clip_image023

· Über die Schaltfläche „Hinzufügen“ werden die Filterbedingungen gesetzt.

clip_image025

clip_image026

· Auswahl erstes Feld links -> „Name“ -> ist gleich -> Text eingeben… -> PreferredName
Hinweis: keine Anführungszeichen eingeben, diese setzt InfoPath automatisch.

Alle Fenster werden mit „OK“ geschlossen. Das Formular kann nun veröffentlicht und verwendet werden.

Der Webservice GetUserProfileByName kann ebenso hilfreich sein für das Auslesen weiterer Felder wie:
UserProfile_GUID, AccountName, FirstName, LastName, PreferredName, WorkPhone, Office, Department, Title, Manager, AboutMe, PersonalSpace, PictureURL, UserName, QuickLinks, WebSite, Assistant, WorkEmail, CellPhone, Fax, HomePhone, PublicSiteRedirect, SPS-Dotted-line, SPS-Peers, SPS-Responsibility, SPS-Skills, SPS-PastProjects, SPS-Interests, SPS-School, SPS-SipAddress, SPS-Birthday, SPS-MySiteUpgrade, SPS-DontSuggestList, SPS-ProxyAddresses, SPS-HireDate, SPS-LastColleagueAdded.

SELECT from PROCEDURE – Prozeduren in Abfragen verwenden

Prozeduren lassen sich nicht in SELECT Statements verwenden. Sie müssen ausgeführt werden. Im Prinzip eine sehr logische Sache, da Prozeduren einerseits mehrere Ergebnistabellen zurückgeben könnten bzw. - wenn nur INSERT, UPDATE oder DELETE Statements ausgeführt werden würden - auch gar nichts zurückgeben müssten.

Ein paar Ausnahmen gibt's allerdings schon ;-)

Der Standardweg wäre, das Ergebnis der Prozedur in eine temporäre Tabelle zu schreiben und mit dieser dann weiter zu arbeiten.

Der Weg Nr. 2 ist der Umweg mittels OPENROWSET: eine AdHoc-Abfrage auf einen SQL Server, in unserem Fall auf den eigenen. Die AdHoc-Abfrage ist natürlich mit allen Einschränkungen zu genießen wie z.B.:

  • Geschwindigkeit
  • Es wird das erste Resultset zurückgegeben, falls es mehrere gäbe

Hier nun die simplen Beispiele

 

--Variante 1 - Temporäre Tabelle

 

create table #t1 (product varchar(50), total int)

insert into #t1

exec CustorderHist 'ALFKI'

 

select * from #t1

 

--Variante 2 - OPENROWSET

 

 

Select * from     OPENROWSET('SQLNCLI',

                'Server=.;Trusted_Connection=yes;', --. Für lokale Std Instanz

                'exec northwind.dbo.custOrderHist ''ALFKI''') ProcTab

 INNER JOIN Products P on P.ProductName = ProcTab.Productname

    where

        ProcTab.Total > 10

 

Cool, gell?

 


Wissen aufbauen und Microsoft Surface Pro 3 kassieren

Month List