Archiv vom 6. September 2018

06 | 09 | 2018

CIM Lingen 2018 – Folien und Code-Snippets zu meinem Vortrag

Geschrieben von um 16:35 Uhr

Am 01.09.2018 durfte ich auf der diesjährigen CIM Lingen einen Vortrag zum Thema „Running Scripts in the Enterprise“ halten. Ein Thema freilich für strategische Beratungen und lange Refactoring-Projekte, das ich versucht habe, in 45 Minuten zu pressen.

Dennoch konnte ich es mir nicht nehmen lassen, zwei Live Demos einzubauen, und da diese nicht in den Folien waren, poste ich die Code Snippets für Windows und Linux ebenfalls hier. Enjoy!

Demo 1: Konfiguration zu Beginn des Skriptes lesen

Sowohl in der Test- als auch in der Produktionsumgebung ist ein Webserver vorhanden, der auf den Namen https://simpleconfig.it-pro-berlin.de hört (da DNS in Test und in Prod aber voneinander unabhängig ist, löst sich dieser Name zu unterschiedlichen Maschinen auf). Dieser Server stellt die Datei config.xml mit dem folgenden Inhalt zur Verfügung:

<scriptconfig>
	<runglobal>true</runglobal>
	<sql dbserver="ciminfra.cim.it-pro-berlin.de" dbname="ScriptsDB" dbuser="confread" dbpasswd="confread">Production SQL Database</sql>
</scriptconfig>

Die Aufgabe lautet, den Wert von „runglobal“ auszuwerten und, falls er auf „true“ steht, mit den in der nächsten Zeile enthaltenen Verbindungsdaten zu einem SQL Server zu verbinden und aus der Tabelle „SETTINGS“ ddie Einstellung „ENVIRONMENT“ auszulesen. Die Tabelle enthält zwei Spalten: „settingname“ und „settingvalue“.

Hier ist die Lösung in PowerShell. Dieser Code funktioniert unverändert in Windows PS, PSCore auf Windows und PSCore auf Linux.

function Get-CIMConfig {
    $config_url = "https://simpleconfig.it-pro-berlin.de/config.xml"
    $res = $null
    try {
        $http_response = Invoke-WebRequest -Uri $config_url -TimeoutSec 10
        if ($http_response.StatusCode -eq 200) {
            $config = ($http_response.content)
            if ($config.scriptconfig.runglobal -eq "true") {
                $res = New-Object PSObject -Property @{
                    "dbserver" = $config.scriptconfig.sql.dbserver
                    "dbname" = $config.scriptconfig.sql.dbname
                    "dbuser" = $config.scriptconfig.sql.dbuser
                    "dbpasswd" = $config.scriptconfig.sql.dbpasswd
                }
            }
        }
    } catch {}
    $res
}

#Jedes Skript beginnt mit
$cc = Get-CIMConfig
if ($cc) {
    Write-Host "Ausführung gestattet..."
    $dbconnstr = "Server=$($cc.dbserver);Database=$($cc.dbname);uid=$($cc.dbuser);pwd=$($cc.dbpasswd)"
    $dbconn = New-Object System.Data.SqlClient.SqlConnection
    $dbconn.ConnectionString = $dbconnstr
    $dbconn.Open() | Out-Null
    $dbcmd = New-Object System.Data.SqlClient.SqlCommand
    $dbcmd.Connection = $dbconn
    $dbcmd.CommandText = "SELECT settingvalue FROM SETTINGS WHERE settingname='ENVIRONMENT'"
    $dbres = $dbcmd.ExecuteReader()
    $dbtable = New-Object System.Data.DataTable
    $dbtable.Load($dbres)
    Write-Host ("Aktuelle Umgebung: {0}" -f $dbtable.Rows[0]['settingvalue'])
}

In Produktion würde man natürlich noch ein wenig mehr Überprüfung und Logging einbauen, aber das ist der wesentliche Teil. Dieser Code benötigt keine externen Module oder Bibliotheken.
In VBS ist der Code etwas sperriger, macht aber was er soll und liefert das gewünschte Ergebnis:

strConfigURL = "https://simpleconfig.it-pro-berlin.de/config.xml"
Set wshShell = CreateObject("WScript.Shell")
strConfigLocal = wshShell.ExpandEnvironmentStrings("%TEMP%") + "\config.xml"
wshShell.Run("cmd.exe /C del /q " + strConfigLocal)

Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
objHTTP.Open "GET", strConfigURL, False
objHTTP.Send
If objHTTP.Status = 200 Then
    Dim objStream
    Set objStream = CreateObject("ADODB.Stream")
    With objStream
        .Type = 1 'adTypeBinary
        .Open
        .Write objHTTP.ResponseBody
        .SaveToFile strConfigLocal
        .Close
    End With
    Set objStream = Nothing
End If

Set xmlDoc = CreateObject("Microsoft.XMLDOM")
xmlDoc.Async = "False"
xmlDoc.Load(strConfigLocal)
Set colNodes = xmlDoc.SelectNodes("//scriptconfig/runglobal")
For Each objNode in colNodes
    runglobal = objNode.Text
Next
If runglobal = "true" Then
    Set colNodes = xmlDoc.SelectNodes("//scriptconfig/sql")
    For Each objNode in colNodes
        dbserver = objNode.Attributes.GetNamedItem("dbserver").Text
        dbname = objNode.Attributes.GetNamedItem("dbname").Text
        dbuser = objNode.Attributes.GetNamedItem("dbuser").Text
        dbpasswd = objNode.Attributes.GetNamedItem("dbpasswd").Text
    Next
    strDBConnStr = "Provider=SQLOLEDB;Data Source=" & dbserver & ";Initial Catalog=" & dbname & ";User ID=" & dbuser & ";Password=" & dbpasswd
End If
'WScript.Echo(strDBConnStr)
Set objDBConn = CreateObject("ADODB.Connection")
objDBConn.Open(strDBConnStr)
Set objRS = CreateObject("ADODB.Recordset")
objRS.Open "SELECT settingvalue FROM SETTINGS WHERE settingname='ENVIRONMENT'", objDBConn, 3, 3
objRS.MoveFirst
WScript.Echo("Aktuelle Umgebung: " & objRS.Fields.Item("settingvalue"))
objRS.Close()
objDBConn.Close()

Die Shell auf Linux (geht in POSIX und in BASH) braucht zwei externe Pakete, XMLLINT und FreeTDS, danach funktioniert es mit

#!/bin/sh
wget -q "https://simpleconfig.it-pro-berlin.de/config.xml" -O ~/config.xml
runglobal=$(xmllint --xpath "//scriptconfig/runglobal/text()" ~/config.xml)
if [ $runglobal = true ]
    then
        echo "Ausführung gestattet..."
        dbserver=$(xmllint --xpath "string(//scriptconfig/sql/@dbserver)" ~/config.xml)
        dbname=$(xmllint --xpath "string(//scriptconfig/sql/@dbname)" ~/config.xml)
        dbuser=$(xmllint --xpath "string(//scriptconfig/sql/@dbuser)" ~/config.xml)
        dbpasswd=$(xmllint --xpath "string(//scriptconfig/sql/@dbpasswd)" ~/config.xml)
        env=$(bsqldb -U $dbuser -P $dbpasswd -S $dbserver -D $dbname -i sico1.sql 2>&-)
        echo "Umgebung: " $env
fi

Die Datei sico1.sql könnte man natürlich dynamisch aus dem Skript generieren, für den Zweck der Demo ist sie aber statisch und hat den folgenden wenig überraschenden Inhalt:

SELECT settingvalue FROM SETTINGS WHERE settingname='ENVIRONMENT'

An der CMD-Shell schaut es natürlich etwas mau aus, dort hat man sich in der Vergangenheit ja immer wieder mit einer selbstgeschriebenen EXE beholfen. Man kann natürlich auch das o.g. VBS-Skript zu Begimn des CMD-Batchscripts bemühen.

Demo 2: Auf einen SYSLOG-Server schreiben

Einträge auf einem SYSLOG-Server erzeugen, das schaffen Skriptsprachen auch. PowerShell (auch Core) nutzt die System.Net-Klassen:

$syslog_server = 'ciminfra.cim.it-pro-berlin.de'
$Log_Message = 'PowerShell:TEST'
#0=EMERG 1=Alert 2=CRIT 3=ERR 4=WARNING 5=NOTICE  6=INFO  7=DEBUG
$Log_Severity = '1'
#(16-23)=LOCAL0-LOCAL7
$Log_Facility = '22'
# Calculate the priority
$Log_Prio = ([int]$Log_Facility * 8) + [int]$Log_Severity
$Sender = $env:COMPUTERNAME
#Time format the SW syslog understands
$TS = Get-Date -Format "MMM dd HH:mm:ss"
# Assemble the full syslog formatted message
$FullSyslogMessage = "<{0}>{1} {2} {3}" -f $Log_Prio, $TS, $Sender, $Log_Message
$FullSyslogMessage
# create an ASCII Encoding object
$Encoding = [System.Text.Encoding]::ASCII
# Convert into byte array representation
$ByteSyslogMessage = $Encoding.GetBytes($FullSyslogMessage)

$UDPCLient = New-Object System.Net.Sockets.UdpClient
$UDPCLient.Connect($syslog_server, 514)
$UDPCLient.Send($ByteSyslogMessage, $ByteSyslogMessage.Length)
$UDPCLient.Close()

VBS und CMD-Shell müssen hier, wenig überraschend, ohne externe Unterstützung passen, die BASH-Shell hingegen ist der absolute Champion (das gilt aber nicht für die POSIX-Shell):

#!/bin/bash
msg="Linux_bash:TESTLOG Definitive"
facility=21 #local5
severity=4 #WARN
prio=$((8 * $facility + $severity))
syslogserver="ciminfra.cim.it-pro-berlin.de"
hname=$(hostname)
ts=$(date "+%b %d %H:%M:%S")
syslogmsg="<$prio>$ts $hname $msg"
echo "$syslogmsg" > /dev/udp/$syslogserver/514

Folien von der CIM

Und hier die Folien zum Download:

Tags » , , , , , «

+