Content moved

Beginning in November 2018 I decided to move this blog and my wordpress multisite construction from my own v-server to a hosted wordpress.com instance.

Because of a lack of time, the blogging was not as much as I wished. So the content moved from a payed server to a hosted page at no cost.

Some old pictures and links may not work properly anymore after the migration.

Until now, it’s just a collection of old posts.

Infoblox IP Reservierung via WebAPI in Powershell auslesen

Mit folgendem Powershell Skript, kann man über Servername, Domäne und VLAN ID eine Adressreservierung auf der Infoblox auslesen.

Die Abwicklung erfolgt über die WebAPI der Infoblox. Die Zugangsdaten stehen der Einfachheit halber im Skript, können aber natürlich auch via Übergabe oder Eingabe abgefragt werden.

Die VLAN ID wird via Extensible Attributes abgefragt, welche natürlich gepflegt sein sollten.
Als Rückgabe erscheint ein Objekt, mit IP-Adresse, Netzwerk, Maske, Gateway, VLAN ID und Typ der gefundenen Reservierung / IP Info

 

-> .get-infoblox-data.ps1 -ServerName "xyz" -DNSDomain "domain.local" -vlanID "10"

 

[CMDletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$ServerName,
[Parameter(Mandatory = $true, Position = 1)]
[string]$DNSDomain,
[Parameter(Mandatory = $true, Position = 2)]
[string]$vlanid
)

$global:ibAPIversion = "v2.7"
$global:ibFQDN = "infoblox_url"
$global:uriStart = "https://$global:ibFQDN/wapi/$global:ibAPIversion"
$pwd = ''
$username = ""
$secPW = $pwd | ConvertTo-SecureString  -asPlainText -Force
$global:cred = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $secPW
$global:ityp=""

function get-fixed-addr ($vlanid, $ServerName, $DNSDomain) {
write-host "$ServerName.$DNSDomain"
try{
$fixedaddrs = Invoke-RestMethod -uri "$uriStart/fixedaddress?*SA_VLAN=$vlanid&_return_fields=options,name,comment,ipv4addr,extattrs&_max_results=1500"  -Credential $cred
$t = $fixedaddrs | ?{($_.name -like "*$ServerName.$DNSDomain*")}
$global:ityp="Reservation"
return $t.ipv4addr
}catch{
catchIBReturnError
return
}
}
function convert-prefix($prefix){
switch ($prefix) {
0{ "0.0.0.0" }
1{ "128.0.0.0" }
2{ "192.0.0.0" }
3{ "224.0.0.0" }
4{ "240.0.0.0" }
5{ "248.0.0.0" }
6{ "252.0.0.0" }
7{ "254.0.0.0" }
8{ "255.0.0.0" }
9{ "255.128.0.0" }
10{ "255.192.0.0" }
11{ "255.224.0.0" }
12{ "255.240.0.0" }
13{ "255.248.0.0" }
14{ "255.252.0.0" }
15{ "255.254.0.0" }
16{ "255.255.0.0" }
17{ "255.255.128.0" }
18{ "255.255.192.0" }
19{ "255.255.224.0" }
20{ "255.255.240.0" }
21{ "255.255.248.0" }
22{ "255.255.252.0" }
23{ "255.255.254.0" }
24{ "255.255.255.0" }
25{ "255.255.255.128" }
26{ "255.255.255.192" }
27{ "255.255.255.224" }
28{ "255.255.255.240" }
29{ "255.255.255.248" }
30{ "255.255.255.252" }
31{ "255.255.255.254" }
32{ "255.255.255.255" }
}
}
function catchIBReturnError{
$response = $_.Exception.Response
if ($response.StatusCode -eq [System.Net.HttpStatusCode]::BadRequest) {
$stream = $response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Verbose $responseBody
$wapiErr = ConvertFrom-Json $responseBody
throw [Exception] "$($wapiErr.Error)"
$wapiErr
} else {
throw
}
}
function get-ip-from-arecord($ServerName,$DNSDomain){
#gibt es schon einen a-record?
try{
$infobloxHost = Invoke-RestMethod -uri "$global:uriStart/record:host?name~=$ServerName.$DNSDomain" -Credential $global:cred
$global:ityp="Host-Record"
#wechel ip?
$ip = $infobloxHost[0].ipv4addrs.ipv4addr
}catch{
catchIBReturnError
return
}
#gibt es schon einen host-record, wenn keinen a-record?
try{
if(!($infobloxHost)){
$infobloxHost = Invoke-RestMethod -uri "$global:uriStart/record:a?name~=$ServerName.$DNSDomain" -Credential $global:cred
$global:ityp="A-Record"
$ip = $infobloxHost[0].ipv4addr
}
}catch{
catchIBReturnError
return
}
return $ip
}
function get-network-from-ip($ip){
#erweiterte infos zur ip
try{
$infobloxIPv4Address = Invoke-RestMethod -uri "$global:uriStart/ipv4address?ip_address=$ip" -Credential $global:cred
#netzwerk
$ipNetwork = $infobloxIPv4Address[0].network
}catch{
catchIBReturnError
return
}
return $ipNetwork
}
function get-gateway-from-network ($ipNetwork) {
#gateway
try{
$infobloxGateway = Invoke-RestMethod -uri "$global:uriStart/network?ipv4addr=$ipNetwork&_return_fields=options,extattrs" -Credential $global:cred
$routers = $infobloxGateway[0].options | ?{$_.name -eq "routers"}
$routers = $routers.value
$vlan = $infobloxGateway[0].extattrs.SA_VLAN.value
}catch{
catchIBReturnError
return
}
return $routers, $vlan
}

#ip bekommen, sollte Reservierung, Host, a-record vorhanden sein
$returnIP = get-fixed-addr $vlanid $ServerName $DNSDomain
if(!($returnIP)){
write-host 'Reservierung nicht gefunden, suche nach Host- oder A-Record'
$returnIP = get-ip-from-arecord $ServerName $DNSDomain
}

#wenn ip vorhanden, netzwerk holen
if($returnIP){
$returnNW = get-network-from-ip $returnIP
$returnNoIP = "FALSE"
}else{$returnNoIP = "TRUE";$global:ityp=""}

#wenn netzwerk geholt, gateway finden
if($returnNW){
$returnGW = get-gateway-from-network $returnNW
$returnVLAN = $returnGW[1]
$returnGW = $returnGW[0]
$returnNWtmp = ($returnNW.split("/"))
$returnNW = ($returnNW.split("/"))["0"]
$returnSNM = convert-prefix $returnNWtmp["1"]
}

#werte zurückgeben
$ipObject = "" | Select IP,NW,SNM,GW,VLAN,Typ,NOIP
$ipObject.IP = $returnIP
$ipObject.NW = $returnNW
$ipObject.SNM = $returnSNM
$ipObject.GW = $returnGW
$ipObject.VLAN = $returnVLAN
$ipObject.Typ = $global:ityp
$ipObject.NOIP = $returnNoIP
write-host $ipObject
return $ipObject

Infoblox IP Reservierung via WebAPI in Powershell

Mit folgendem Powershell Skript, kann man über Servername, Domäne und VLAN ID eine Adressreservierung auf der Infoblox vornehmen.

Die Abwicklung erfolgt über die WebAPI der Infoblox. Die Zugangsdaten stehen der Einfachheit halber im Skript, können aber natürlich auch via Übergabe oder Eingabe abgefragt werden.

Die VLAN ID wird via Extensible Attributes abgefragt, welche natürlich gepflegt sein sollten.

 

-> .set-infoblox-data.ps1 -ServerName "xyz" -DNSDomain "domain.local" -vlanID "10"

 

[CMDletBinding()]
param (
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$ServerName,
    [Parameter(Mandatory = $true, Position = 1)]
    [string]$DNSDomain,
    [Parameter(Mandatory = $true, Position = 2)]
    [string]$vlandID
)

$ibAPIversion = "v2.7"
$ibFQDN = "infoblox_url"
$uriStart = "https://$ibFQDN/wapi/$ibAPIversion"
$pwd = ''
$username = ""
$secPW = $pwd | ConvertTo-SecureString  -asPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList $username, $secPW

function catchIBReturnError{
    $response = $_.Exception.Response
    if ($response.StatusCode -eq [System.Net.HttpStatusCode]::BadRequest) {
        $stream = $response.GetResponseStream()
        $reader = New-Object System.IO.StreamReader($stream)
        $reader.BaseStream.Position = 0
        $reader.DiscardBufferedData()
        $responseBody = $reader.ReadToEnd();
        Write-Verbose $responseBody
        $wapiErr = ConvertFrom-Json $responseBody
        throw [Exception] "$($wapiErr.Error)"
    } else {
        throw
    }
}

try{
    $network = Invoke-RestMethod -uri "$uriStart/network?*SA_VLAN=$vlandID&_return_fields=options,extattrs" -Credential $cred
  }catch{
      catchIBReturnError
  }
if(!($network)){
    throw "Kein passendes Netzwerk mit der VLAN ID $vlandID gefunden. Extattrs gepflegt?"
}
#json string für reservierung
###############################
$Body = @{  name="$ServerName.$DNSDomain";
            ipv4addrs= @(
                @{
                    ipv4addr="func:nextavailableip:$($network._ref)"
                }
            )
            view = "WIE AUCH IMMER EURE ANSICHT HEIßT";
        }

$JSON = $Body | ConvertTo-json
####################################

#rückgabe abfangen wenn error
try{
    $reservierung = Invoke-RestMethod -uri "$uriStart/record:host?_return_fields=ipv4addrs" -Method Post -Credential $cred -Body $JSON -ContentType "application/json" -Verbose
}catch{
    catchIBReturnError
}
return "Reservierung wurde erfolgreich angelegt"

Fritzbox Gäste WLAN mit Alexa steuern

Im Folgenden wird erläutert, wie man mit einem selbst gebauten Alexa Skill sein Gäste WLAN einer Fritzbox ein- und ausschalten kann. Ebenso kann das Passwort für das WLAN vorgelesen werden.

Voraussetzungen

Um das Steuern der Fritzbox zu ermöglichen, müssen einige Punkte erfüllt sein.

  1. Man braucht einen Developer Account bei Amazon sowie einen AWS Account.
  2. Da die Steuerung über ein PHP Skript realisiert wird, wird ebenfalls ein lokaler Webserver im Netzwerk benötigt. In diesem Fall wird ein Raspberry PI verwendet.
  3. Dieser Webserver muss aus dem Internet erreichbar sein, damit Alexa darauf zugreifen kann.
  4. Da der Skill auf die eigenen Bedürfnisse angepasst werden muss (Netzwerk, IP Adressen, Benutzernamen, Passwörter etc.), wird der Skill nicht im Store auffindbar sein und nur für einen einzigen Anwendungsfall funktionieren.

Fritzbox Benutzer einrichten

Die im Folgenden benutzte SOAP Funktion, die die Anfragen an die Fritzbox stellt, benötigt zur Authentifizierung einen Benutzernamen bzw. ein Passwort. Daher sollte im Vorhinein ein neuer Benutzer dafür angelegt werden. Es geht auch das Standardpasswort der Fritzbox Oberfläche, aber Safety First 😉

https://fritz.box/ bzw. die IP der Box aufrufen. Unter System – Fritz!Box Benutzer kann ein neuer Benutzer angelegt werden:

Die hier angegebenen Daten werden nachher für die PHP Skripte benötigt.

Raspberry einrichten / Apache installieren

In meinem Fall wird ein Raspberry PI für die Ausführung des Webservers benutzt.

Falls noch nicht schon vorhanden, müssen folgende Pakete installiert werden:

apt-get install apache2 php7.0 php7.0-soap

Nach der Installation kann noch das SSL Modul aktiviert werden, allerdings wird Alexa nur gültige Zertifikate akzeptieren. Selbst signierte Zertifikate werden nicht funktionieren. Aus diesem Grund bleiben wir hier bei einer unverschlüsselten Verbindung.

Der Standardpfad ins Webverzeichnis ist /var/www/html

Das wird umgemappt auf /var/www. Kann man machen, muss man aber nicht. Es ist später allerdings wichtig, dass man weiß, wie die URL zu den Skripten lautet. Für die WLAN Skripte lege ich ein neues Verzeichnis an.

mkdir /var/www/wlan
nano /etc/apache2/sites-enabled/000-default.conf

aus

DocumentRoot /var/www/html

wird

DocumentRoot /var/www

PHP Skript zum Ein- und Ausschalten

In das Verzeichnis werden nun zwei Skripte abgelegt. Eins für das Einschalten des Gäste WLANs und eins für das Ausschalten.

WLAN aus:

nano /var/www/wlan/wlan_aus.php

<?php $client1 = new SoapClient(null,array('location'=> "http://192.168.178.1:49000/upnp/control/wlanconfig2", // IP der FritzBox anpassen
										'uri'		=> "urn:dslforum-org:service:WLANConfiguration:2",
										'soapaction'	=> "urn:dslforum-org:service:WLANConfiguration:3#SetEnable",
										'noroot'	=> True,
										'login'		=> "username", // Username, ist aber irrelevant
										'password'	=> "password" // Passwort
));
$client1->SetEnable(new SoapParam(0, 'NewEnable'));
?>

WLAN ein:

nano /var/www/wlan/wlan_ein.php

<?php $client1 = new SoapClient(null,array('location'=> "http://192.168.0.1:49000/upnp/control/wlanconfig2",
										'uri'			=> "urn:dslforum-org:service:WLANConfiguration:2",
										'soapaction'	=> "urn:dslforum-org:service:WLANConfiguration:2#SetEnable",
										'noroot'		=> True,
										'login'			=> "username",
										'password'		=> "password"
	));
$client1->SetEnable(new SoapParam(1, 'NewEnable'));
?>

Jeweils anzupassen ist hierbei die IP Adresse der Fritzbox sowie das Passwort für den Zugang. Der Benutzername kann auch hinterlegt werden, allerdings wird dieser bei internen Abfragen ignoriert.

Wichtig zu wissen ist, dass die verwendeten Parameter wlanconfig2, WLANConfiguration:2 und WLANConfiguration:2#SetEnable abweichen können. Sie beziehen sich auf die von der Fritzbox vergebene Nummerierung der WLAN Netze. Nummer 1 ist das “normale” WLAN und Nummer 2 sollte das für das Gäste WLAN gelten. Kontrollieren kann man es auf der XML Seite der TR-064 API: http://fritz.box:49000/tr64desc.xml

Dort sind alle “WLAN Nummern” aufgeführt

Anschließend kann man die Skripte via Browser aufrufen und überprüfen, ob das WLAN auf der Fritzbox ausgeschaltet wurde oder nicht. Fehler findet man im Apache Error Log unter /var/log/apache2/error.log. Sollte es Authentifizierungsprobleme o.ä. geben, findet man die Meldungen dort. Wichtig ist auch, dass das PHP SOAP Modul aktiviert ist. Ansonsten kommt es zu Unknown Class Exceptions im PHP.

Weiterleitung der externen URL auf den Webserver

Damit man von Außerhalb auf die URL zugreifen kann, muss eine Portweiterleitung auf der Fritzbox eingerichtet werden. Internet – Freigaben – Portfreigaben aufrufen und eine neue Weiterleitung hinterlegen. Gerät auswählen, externen Port und internen Port des Geräts auswählen, fertig. In dem Fall wurde der externe Port 81 auf den http Port vom Raspberry gemappt.

Anschließend sollte man über http://Externe-IP:81/wlan/wlan_aus.php das Skript aufrufen können. Damit man nicht jeden Tag die IP im Alexa Skill ändern muss, macht eine Nutzung von DynDNS oder der kostenlosen MyFritz Variante Sinn!

Damit wären alle grundlegenden Vorbereitungen zur Steuerung der Fritzbox gegeben. Es fehlt noch die Anbindung als Alexa Skill.

Lambda Funktion erstellen

Man benötigt nun den Account fürs Amazon AWS.

Damit meldet man sich bei der Management Console an (https://aws.amazon.com/de/console/) und erstellt eine neue Lambda Funktion

Anschließend wählt man Create Function, klickt auf Blueprints und wählt “alexa-skill-kit-sdk-factskill“.

Nun gibt man der Funktion einen Namen, wählt “Create new role from template” und nennt die Rolle lambda_basic_execution.

Der nun vorgefertigte Code wird wiefolgt abgeändert:

var speechOutput;
var reprompt;

var myURL = 'http://url.myfritz.net:81/wlan/';
var myWLAN = 'Schnorrer Hotspot';
var myPass = "<break time='1s'/> Passwort<break time='1s'/> Der Buchstabe von jedem neuen Wort wird groß geschrieben.<break time='1s'/> ";

var welcomeOutput = "Hallo! Na, wie geht's denn so?";
// Womit kann ich behilflich sein? Für einen Überblick, was ich erledigen kann, sage Menü oder Liste.
var welcomeReprompt = "Wie kann ich helfen? Sage z.B. Menü oder Hilfe";

 // 2. Skill Code =======================================================================================================
"use strict";
var Alexa = require('alexa-sdk');
var APP_ID = '';  // TODO replace with your app ID (OPTIONAL).
var speechOutput = '';
var handlers = {
    'LaunchRequest': function () {
          this.emit(':ask', welcomeOutput, welcomeReprompt);
    },
	'AMAZON.HelpIntent': function () {
        speechOutput = 'Dieser Skill kann dir z.B. das Passwort zum Gäste WLAN vorlesen. Sage dazu, Alexa frage Gäste WLAN, wie lautet das Passwort? Alternativ kannst du zum Beispiel das Gäste WLAN ein oder ausschalten. Womit kann ich dir nun dienen?';
        reprompt = 'probiere es mit nenne mir das Passwort';
        this.emit(':ask', speechOutput, reprompt);
    },
    'AMAZON.CancelIntent': function () {
        speechOutput = 'ok';
        this.emit(':tell', speechOutput);
    },
    'AMAZON.StopIntent': function () {
        speechOutput = 'ok, hau rein';
        this.emit(':tell', speechOutput);
    },
    'SessionEndedRequest': function () {
        speechOutput = '';
        //this.emit(':saveState', true);
        this.emit(':tell', speechOutput);
    },
     'AMAZON.YesIntent': function () {
        speechOutput = "OK ich wiederhole. " + myPass + " Soll ich es noch einmal wiederholen?";
        this.emit(':ask', speechOutput, speechOutput);
    },
     'AMAZON.NoIntent': function () {
        speechOutput = 'OK dann wünsche ich deinen Gästen viel Spaß beim surfen im Internet';
        this.emit(':tell', speechOutput);
    },
	"MenuIntent": function () {
	speechOutput = "Sage z.B. Gäste WLAN ein oder ausschalten. Du kannst mich auch fragen, wie ist das Passwort. Womit kann ich dir nun dienen?";
        this.emit(":ask", speechOutput, speechOutput);
    },
	"WLANIntent": function () {
	speechOutput = "Das Passwort lautet" + myPass +" Soll ich es noch einmal wiederholen?";
        this.emit(":ask", speechOutput, speechOutput);
    },
	"WLANOnIntent": function () {
		speechOutput = "Ok, ich habe das Gäste WLAN aktiviert. Der Name lautet " + myWLAN + ". Wenn du das Passwort brauchst, sag einfach: Alexa frage Gäste WLAN, wie lautet das Passwort?";

        var url = 'wlan_ein.php';
        getValue(url,(result)=>{
            this.emit(":tell", speechOutput);
        });

    },
    "WLANOffIntent": function () {
	speechOutput = "Ok, ich habe nun das Gäste WLAN deaktiviert";
        var url = 'wlan_aus.php';
        getValue(url,(result)=>{

            this.emit(":tell", speechOutput);
        });
    },
	'Unhandled': function () {
        speechOutput = "Ich habe dich leider nicht verstanden. Kannst du das Bitte noch einmal wiederholen?";
        this.emit(':ask', speechOutput, speechOutput);
    }
};

function getValue(loc,cb) {
    var https = require('http');
    let endpoint = myURL + loc;
    let something = "";
    let body = "";
    https.get(endpoint, (response) => {
        response.on('data', (chunk) => {
            body += chunk;
        });
        response.on('end', () => {
            cb(null);
        });
    });
}

exports.handler = (event, context) => {
    var alexa = Alexa.handler(event, context);
    alexa.APP_ID = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};


function resolveCanonical(slot){
	//this function looks at the entity resolution part of request and returns the slot value if a synonyms is provided
   var canonical = '';
    try{
		canonical = slot.resolutions.resolutionsPerAuthority[0].values[0].value.name;
	}catch(err){
	    console.log(err.message);
	    canonical = slot.value;
	}
	return canonical;
}

function delegateSlotCollection(){
  console.log("in delegateSlotCollection");
  console.log("current dialogState: "+this.event.request.dialogState);
    if (this.event.request.dialogState === "STARTED") {
      console.log("in Beginning");
	  var updatedIntent= null;
	  // updatedIntent=this.event.request.intent;
      //optionally pre-fill slots: update the intent object with slot values for which
      //you have defaults, then return Dialog.Delegate with this updated intent
      // in the updatedIntent property
      //this.emit(":delegate", updatedIntent); //uncomment this is using ASK SDK 1.0.9 or newer

	  //this code is necessary if using ASK SDK versions prior to 1.0.9
	  if(this.isOverridden()) {
			return;
		}
		this.handler.response = buildSpeechletResponse({
			sessionAttributes: this.attributes,
			directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null),
			shouldEndSession: false
		});
		this.emit(':responseReady', updatedIntent);

    } else if (this.event.request.dialogState !== "COMPLETED") {
      console.log("in not completed");
      // return a Dialog.Delegate directive with no updatedIntent property.
      //this.emit(":delegate"); //uncomment this is using ASK SDK 1.0.9 or newer

	  //this code necessary is using ASK SDK versions prior to 1.0.9
		if(this.isOverridden()) {
			return;
		}
		this.handler.response = buildSpeechletResponse({
			sessionAttributes: this.attributes,
			directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null),
			shouldEndSession: false
		});
		this.emit(':responseReady');

    } else {
      console.log("in completed");
      console.log("returning: "+ JSON.stringify(this.event.request.intent));
      // Dialog is now complete and all required slots should be filled,
      // so call your normal intent handler.
      return this.event.request.intent;
    }
}


function randomPhrase(array) {
    // the argument is an array [] of words or phrases
    var i = 0;
    i = Math.floor(Math.random() * array.length);
    return(array[i]);
}
function isSlotValid(request, slotName){
        var slot = request.intent.slots[slotName];
        //console.log("request = "+JSON.stringify(request)); //uncomment if you want to see the request
        var slotValue;

        //if we have a slot, get the text and store it into speechOutput
        if (slot && slot.value) {
            //we have a value in the slot
            slotValue = slot.value.toLowerCase();
            return slotValue;
        } else {
            //we didn't get a value in the slot.
            return false;
        }
}

//These functions are here to allow dialog directives to work with SDK versions prior to 1.0.9
//will be removed once Lambda templates are updated with the latest SDK

function createSpeechObject(optionsParam) {
    if (optionsParam && optionsParam.type === 'SSML') {
        return {
            type: optionsParam.type,
            ssml: optionsParam['speech']
        };
    } else {
        return {
            type: optionsParam.type || 'PlainText',
            text: optionsParam['speech'] || optionsParam
        };
    }
}

function buildSpeechletResponse(options) {
    var alexaResponse = {
        shouldEndSession: options.shouldEndSession
    };

    if (options.output) {
        alexaResponse.outputSpeech = createSpeechObject(options.output);
    }

    if (options.reprompt) {
        alexaResponse.reprompt = {
            outputSpeech: createSpeechObject(options.reprompt)
        };
    }

    if (options.directives) {
        alexaResponse.directives = options.directives;
    }

    if (options.cardTitle && options.cardContent) {
        alexaResponse.card = {
            type: 'Simple',
            title: options.cardTitle,
            content: options.cardContent
        };

        if(options.cardImage && (options.cardImage.smallImageUrl || options.cardImage.largeImageUrl)) {
            alexaResponse.card.type = 'Standard';
            alexaResponse.card['image'] = {};

            delete alexaResponse.card.content;
            alexaResponse.card.text = options.cardContent;

            if(options.cardImage.smallImageUrl) {
                alexaResponse.card.image['smallImageUrl'] = options.cardImage.smallImageUrl;
            }

            if(options.cardImage.largeImageUrl) {
                alexaResponse.card.image['largeImageUrl'] = options.cardImage.largeImageUrl;
            }
        }
    } else if (options.cardType === 'LinkAccount') {
        alexaResponse.card = {
            type: 'LinkAccount'
        };
    } else if (options.cardType === 'AskForPermissionsConsent') {
        alexaResponse.card = {
            type: 'AskForPermissionsConsent',
            permissions: options.permissions
        };
    }

    var returnResult = {
        version: '1.0',
        response: alexaResponse
    };

    if (options.sessionAttributes) {
        returnResult.sessionAttributes = options.sessionAttributes;
    }
    return returnResult;
}

function getDialogDirectives(dialogType, updatedIntent, slotName) {
    let directive = {
        type: dialogType
    };

    if (dialogType === 'Dialog.ElicitSlot') {
        directive.slotToElicit = slotName;
    } else if (dialogType === 'Dialog.ConfirmSlot') {
        directive.slotToConfirm = slotName;
    }

    if (updatedIntent) {
        directive.updatedIntent = updatedIntent;
    }
    return [directive];
}

Wichtig hierbei sind die Variablen myURL, myWLAN, myPass. Diese passt man für dich an.

Die Ausgaben von Alexa kann man in den jeweiligen Intents anpassen. Sollte die Webseite mit den PHP Skripten nicht SSL verschlüsselt sein, muss der Abschnitt “var https = require(‘https’);” abgeändert werden nach “var https = require(‘http’);”. Die Änderung sollte man vornehmen, wenn man kein offizielles Zertifikat besitzt.

Anschließend muss noch ein Trigger zur Funktion hinzugefügt werden. Dazu von der Liste “Alexa Skill Kit” auswählen und nach rechts verschieben. Sollte “Alexa Skill Kit”, muss man evtl. oben rechts die Region ändern. Bei “N. Virginia” waren die Trigger verfügbar.

Zuletzt kopiert man sich den Amazon Resource Name (ARN), er wird später vom Alexa Skill benötigt.

Alexa Skill erstellen

Man benötigt nun einen Account bei https://developer.amazon.com/ und öffnet https://developer.amazon.com/edw/home.html#/skill/create/, um einen neuen Skill anzulegen.

Beim Anlegen des Skill, vergibt man einen internen Namen und den “Invocation Name”, mit dem der Skill später aufgerufen wird.

Anschließend geht es im “Interaction Model” weiter. Hier wird der Skill Beta Builder genutzt. Im Code Editor wird folgendes hinterlegt:

{
  "intents": [
    {
      "name": "AMAZON.CancelIntent",
      "samples": []
    },
    {
      "name": "AMAZON.HelpIntent",
      "samples": []
    },
    {
      "name": "AMAZON.NoIntent",
      "samples": [
        "Nein",
        "sei ruhig",
        "danke",
        "no"
      ]
    },
    {
      "name": "AMAZON.StopIntent",
      "samples": []
    },
    {
      "name": "AMAZON.YesIntent",
      "samples": [
        "Ja",
        "natürlich",
        "jawohl",
        "sicher",
        "klar",
        "si"
      ]
    },
    {
      "name": "MenuIntent",
      "samples": [
        "Menü",
        "Liste",
        "Übersicht",
        "was kann ich sagen"
      ],
      "slots": []
    },
    {
      "name": "WLANIntent",
      "samples": [
        "wie lautet das Passwort",
        "nach dem Passwort",
        "wie ist das Passwort für das WLAN",
        "wie ist das Passwort für das Gäste WLAN",
        "sage mir das Passwort",
        "nenne mir das Passwort"
      ],
      "slots": []
    },
    {
      "name": "WLANOffIntent",
      "samples": [
        "Gäste WLAN ausschalten",
        "Gäste WLAN deaktivieren",
        "schalte das Gäste WLAN aus"
      ],
      "slots": []
    },
    {
      "name": "WLANOnIntent",
      "samples": [
        "WLAN einschalten",
        "Gäste WLAN einschalten",
        "schalte das Gäste WLAN ein",
        "aktiviere das Gäste WLAN"
      ],
      "slots": []
    }
  ]
}

Anschließend geht man auf “Build Model” und danach auf “Konfiguration”, um wieder zur Skill Ansicht zurück zu gehen. Hier wird nun die Lambda ARN hinterlegt.

Im nächsten Abschnitt “Test”, muss der Schalter aktiviert werden. Ab jetzt kann der Skill von Alexa aufgerufen werden.

Testen

Über ein Tail auf das Apache Access Log kann man gut nachverfolgen, ob die URL aufgerufen wird:

tail /var/log/apache/access.log

Hier gibt es die notwenigen Dateien zum Download.

Outlook Kalender in PST exportieren via Powershell

kurz notiert:

Ein Export des Outlook Kalenders kann via Powershell Skript wie folgt realisiert werden

$Outlook = New-Object -ComObject Outlook.Application
$NS = $Outlook.GetNamespace('MAPI')
$Store = $NS.Stores | ? {$_.displayname -eq "abc@xyz.de"}
$Calendar = $Store.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderCalendar)
#PST Store
$Outlook.Session.AddStore("c:tmptemp.pst")
$PST = $ns.Stores | ? {$_.filepath -eq "c:tmptemp.pst"}
$PSTRoot= $PST.GetRootFolder()
$PSTFolder= $NS.Folders.Item($PSTRoot.Name)
#Export
$Calendar.CopyTo($PST)
#Trennen des Archivs
$NS.GetType().InvokeMember('RemoveStore',[System.Reflection.BindingFlags]::InvokeMethod,$null,$NS,($PSTFolder))

Das Archiv könnte man anschließend auch in ein bestehendes Exchange (Online) Postfach importieren.

#mit exchange (online) verbinden
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential (Get-Credential) -Authentication Basic -AllowRedirection
New-MailboxImportRequest -Mailbox <Name> -FilePath <UNC-Pfad und Name der PST-Datei> -TargetRootFolder <Ordner im Postfach>
#Zwischenstand abfragen
Get-MailboxImportRequest <Name des Importvorgangs> | fl
#Import löschen
Remove-MailboxImportRequest

VMware Lizenzen auslesen und zugehörigen Host finden

Je größer die vSphere Umgebung ist, desto schwerer wird es, einen Überblick über die Lizenzen zu behalten.

Die Weboberfläche des Web Clients zeigt nur die vorhandenen Lizenzen an, nicht aber, wo sie verwendet werden. Dazu müsste man entweder jeden einzelnen ESX durchgehen oder beim Einpflegen der Lizenzen die jeweilige Beschreibung anpassen.

Einfacher ist da der Weg über die VMware PowerCLI.

Der Code

import-module VMware.VimAutomation.Cis.Core
Connect-VIServer -server vcenter.braun.local -Protocol https
$hosts = get-vmhost -name *
$vSphereLicenseInfo = @()
$ServiceInstance = Get-View ServiceInstance
$LicenseManager = Get-View ($ServiceInstance | Select -First 1).Content.LicenseManager
Foreach ($License in ($LicenseManager | Select -ExpandProperty Licenses)) {
	$Details = "" |Select Name, Key, Total, Used, Free, ExpirationDate , Information, Hosts
	$Details.Name= $License.Name
	$Details.Key= $License.LicenseKey
	$Details.Total= $License.Total
	$Details.Used= $License.Used
	$Details.Free = $Details.Total - $Details.Used
	$Details.Information= $License.Labels | Select -expand Value
	$Details.ExpirationDate = $License.Properties | Where { $_.key -eq "expirationDate" } | Select -ExpandProperty Value
	foreach($esx in $hosts){
		if($esx.LicenseKey -eq $License.LicenseKey){
			$Details.Hosts += $esx.Name + " "
		}
	}
	$vSphereLicenseInfo += $Details
}
$vSphereLicenseInfo | Format-Table -AutoSize

Das Ergebnis sieht dann wiefolgt aus:

Erklärungen

Zeile 1: Import der benötigten Module (falls man die Powershell händisch startet und nicht die PowerCLI an sich startet)

Zeile 2: Verbindung zum vCenter aufbauen

Zeile 3: Alle Hosts auslesen, um diese nachher iterativ gegen die Lizenzen laufen zu lassen

Zeile 4-15: Service Instanz Objekt anlegen, um an den Lizenzmanager zu kommen, dann via Schleife über die Lizenzen laufen und die notwendigen Infos auslesen und im Array ablegen.

Zeile 16-20: Zu jeder Lizenz wird anschließend ein Abgleich über alle Hosts gemacht,  um die Hosts zu finden, die die Lizenz verwenden.

 

Wer die Ergebnisse gerne in Excel importieren möchte, kann das bequem mit folgendem Befehl. Die CSV Datei muss dann wie gewohnt importiert werden.

$vSphereLicenseInfo | Export-Csv .licenses.csv

Andere E-Mail Absenderadresse in Office 365/Exchange Online

Es kann sein, dass man mit einer zusätzlichen E-Mail Adresse als Absender in Office 365/Exchange Online Mails versenden möchte. Dafür gibt es verschiedene Möglichkeiten. Eine davon ist das Nutzen von Verteilerlisten.

Dazu muss man als erstes einen Verteiler anlegen. Dies geschieht entweder über das Office 365 Admin Center unter Gruppen oder im Exchange Admin Center unter Empfänger – Gruppen.

Office 365 Admin Center:

Exchange Admin Center (hier mache ich die Konfiguration im Folgenden):

 

Unter Gruppen kann man eine neue Verteilerliste anlegen. Wichtig ist hierbei, dass Absender innerhalb und außerhalb meiner Organisation angehakt wird, damit man unter der Adresse zukünftig auch Mails empfangen kann, die von externen Absendern kommen.

Die restlichen Einstellungen sprechen für sich. Mitglieder und Genehmigungen einstellen, wie man sie braucht.

Um die gewünschten Absender einzurichten, wechselt man in den Punkt Gruppendelegierung. Man muss sich das Recht Senden als für die Gruppe erteilen. Das geht nur in der Exchange-Verwaltungskonsole.

In Outlook kann man jetzt ohne weitere Konfiguration die neue E-Mail-Adresse zum Versand auswählen.

Dazu erstellt man eine neue E-Mail und klingt im von Dialog auf weitere E-Mail Adresse und trägt die Verteilerliste ein.

Zukünftig merkt sich Outlook den Eintrag in der Dropdown-Box des Von:-Feldes.

Danach einfach die Mail versenden und fertig.

Piwik – Umgehen von Ad Blockern

Ad Blocker beeinflussen nicht nur das Anzeigen von nerviger Werbung auf Webseiten, sondern sie blockieren ebenfalls die Analyse Skripte auf den Seiten wie Piwik oder Google Analytics.

Sie blockieren diese Skripte, um bspw. Marketing-Tracking von Drittanbieter-Werbetreibenden, z. B. Google, einzudämmen. Wenn man Piwik Analytics oder eines der meisten anderen selbst gehosteten Systeme verwendet, kann man diese Blockierung allerdings umgehen, indem man die URL umschreibt.

Adblocker suchen auf Webseiten nämlich bspw. nach Keywords in diesen URLs wie http://…/piwik/piwik.js

Diese werden dann an der Ausführung gehindert.

Umschreiben der URL

Zunächst wird die Haupt URL via .htaccess oder Seitenkonfiguration geändert:

RewriteEngine On
RewriteRule ^wieauchimmerdieneueurlheissensoll/(.*) /piwik/piwik.$1 [L]

Der neue Pfad kann nach Belieben geändert werden. Der zweite Pfad muss die eigentliche Installation von Piwik beinhalten.

Ändern Piwik Tracking Code

Anschließend muss der Tracking Code in den Seiten so angepasst werden, dass er auch den neuen Pfad verweist, damit die Ad Blocker nicht mehr anschlagen.

So sollte der Code vor der Änderung aussehen:

<!-- Piwik -->
<script type="text/javascript">
 var _paq = _paq || [];
 /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
 _paq.push(['trackPageView']);
 _paq.push(['enableLinkTracking']);
 (function() {
 var u="//www.example.com/piwik/";
 _paq.push(['setTrackerUrl', u+'piwik.php']);
 _paq.push(['setSiteId', '1']);
 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
 g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
 })();
</script>
<!-- End Piwik Code -->

Und so sollte der Code mit den geänderten Werten aussehen:

<!-- Piwik -->
<script type="text/javascript">
 var _paq = _paq || [];
 _paq.push(['trackPageView']);
 _paq.push(['enableLinkTracking']);
 (function() {
 var u="//www.example.com/wieauchimmerdieneueurlheissensoll/";
 _paq.push(['setTrackerUrl', u+'php']);
 _paq.push(['setSiteId', 2]);
 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
 g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'js'; s.parentNode.insertBefore(g,s);
 })();
</script>
<!-- End Piwik Code -->

Zeile 7: Hier muss die URL auf die geänderte URL angepasst werden, wie sie in der Rewrite-Regel definiert wurde.

Zeile 8,11: Hier wird “piwik.” entfernt. Manche Ad Blocker blockieren piwik.[php language=”/js”][/php] oder einfach das ganze Wort wie durch ABP.

Nach diesen Änderungen sollte Piwik auch Informationen sammeln können, wenn Ad Blocker verwendet werden. Tracker wie Ghostery listen Piwik bspw. nicht mehr als Tracker auf der Seite:

 

 

 

Nextcloud Kontakte Icon deaktivieren

Nextcloud zeigt über das Kontakte Icon in der Menüleiste alle Benutzer der Serverinstanz an. Auch, wenn die Kontakte App deaktiviert ist. Das kann ein Problem darstellen, wenn man mehrer Gruppen verwaltet, die sich untereinander nicht sehen sollen.

Eine Einstellung zur Deaktivierung dieser Funktion oder einer genaueren Festlegung des Verhaltens ist bisher nicht implementiert.

Es gibt zwei Möglichkeiten.

  1. Man deaktiviert in den Einstellungen die Möglichkeit zur Autovervollständigung der Namen. Dann werden keine Kontakte in der Liste angezeigt. Allerdings ist dann eben auch keine Vervollständigung im Teilen Dialog vorhanden.
  2. Man bearbeitet die Datei /core/templates/layout.user.php und kommentiert den Bereich für das Icon aus. Dann hat man keine Möglichkeit mehr auf das Icon zuzugreifen. Allerdings besteht dann immer noch die Möglichkeit, über den Teilen Dialog alle Benutzer zu finden, indem man Teile seines Namens eingibt.

[html]

</div> [/html]

DHCP Migration von Server 2008 R2 zu Server 2016

Die DHCP Migration von Server 2008 R2 zu Server 2016 sollte via netsh oder Powershell durchgeführt werden. Die Migration über die GUI der DHCP Konsole wirft einen Fehler und funktioniert u.U. nicht.

Zunächst muss die aktuelle DHCP Datenbank exportiert werden:

Anmeldung auf dem DHCP Server und Start einer administrativen CMD.

Netsh
DHCP
Server \<DHCP Server IP Addresse>
Export c:tempserver_dhcp-db all

Dadurch wird eine Datei namens c:tempserver_dhcp-db erstellt, welche auf den neuen DHCP Server kopiert werden muss.

Net stop DHCPserver
Del c:windowssystem32DHCPDHCP.mdb
Net start DHCPserver
Netsh
DHCP
Server \<DHCP Server IP Addresse>
Import c:tempserver_dhcp-db
Exit
Net stop DHCPserver
Net start DHCPserver

Danach kann der alte DHCP Server abgeschaltet werden. Die alte Datenbank mit allen Reservierungen sollte nun auf dem neuen Server vorhanden sein.

Import und Export via Powershell

Das Vorgehen kann auch via Powershell durchgeführt werden.

Der alte DHCP Server muss dazu mindestens unter Server 2008 laufen.

Auf dem Zielsystem öffnet man eine administrative Powershell. Darin wird nun das neue DHCP Server Modul genutzt.

Export-dhcpserver –computername ALTER.DHCP.SERVER –leases –file c:tempdhcp.xml –verbose

Nun kann man den alten DHCP Server abschalten und die Einstellungen in den neuen Server einspielen.

Import-dhcpserver –computername NEUER.DHCP.SERVER –leases –file c:tempdhcp.xml –backuppath C:tempBackup -verbose

Der Parameter backuppath ist zwingend. Dort wird die aktuelle DHCP Konfiguration zur Sicherung abgelegt.