Daniel Schädler: Meine ersten Schritte mit Terraform

In diesem Artikel ist die Vorgehensweise beschrieben wie man eine Terraform Umgebung für Tests auf- und wieder abbauen kann. Hierzu habe ich Microsoft Azure verwendet. Diese ist im Rahmen der dotnet user group Seite geschehen um so die Tests mit Browserstack auf einer virtuellen Umgebung durchführen zu können.

Vorausetzungen

Ziel

Das Ziel soll sein, dass eine Testumgebung bestehend aus folgenden Komponenten

  • SQL Server
  • App Service hochfährt, die automatisierten Tests durchläuft und danach gelöscht wird.

Vorgehensweise

Die nachfolgenden Schritte beschreiben die Vorgehensweise in 3 Schritten.

  • Identifizieren der benötigten Ressourcen
  • Identifizieren der Terraform Ressourcen
  • Zusammenführen

Identifizieren der Terraform Ressourcen

Wirf man ein Auge auf die Azure Automation, so sticht die sehr grosse JSON-Datei ins Auge. Sie kann als Anhaltspunkt verwendet werden, muss aber nicht. Schaut man sich das so an, dann sind das eine Menge an Ressourcen, die da Verwendung finden. Nun stellt Terraform unter folgendem Link, die aktuellen Ressourcen für Azure zur Verfügung. Folgende Ressourcen können dafür Verwendung finden:

Zusammenführen

Nun muss das Ganze in einer Terraform Datei zusammengeführt werden. Hierzu wird ein Ordner erstellt, zum Beispiel dotnet-usergroup-bern-terraform-configuration. In diesem Ordner ist dann eine main.tf Datei zu erstellen, die die ganze Terraform Konfiguration beinhaltet. Diese Datei sieht dann wie so aus:

# Configure the dotnet-user-group test environment

terraform{
    required_providers{
        azurerm = {
            source = "hashicorp/azurerm"
            version = ">=2.26"
        }
    }
}

provider "azurerm"{
    features{}
}

resources "azurerm_resource_group" "bddtest"{
    name = "bddtest-dotnet-usergroup"
    location = "switzerlandnorth"
}

// SQL configuration
resource "azurerm_storage_account" "bddtest"{
    name = "bdd-test-dotnet-usergroup"
    resource_group_name = azurerm_resource_group.bddtest.name
    location = azurerm_resource_group.bddtest.location
    account_tier = "Free"
    account_replication_type = "GRS"
}

resource "azurerm_sql_server" "bddtest" {
  name                         = "bddtest-sqlserver"
  resource_group_name          = azurerm_resource_group.bddtest.name
  location                     = azurerm_resource_group.bddtest.location
  version                      = "12.0"
  administrator_login          = "4dm1n157r470r"
  administrator_login_password = "4-v3ry-53cr37-p455w0rd"
}

resource "azurerm_mssql_database" "bddtest" {
  name           = "bddtest-dnugberndev"
  server_id      = azurerm_sql_server.bddtest.id
  collation      = "SQL_Latin1_General_CP1_CI_AS"
  license_type   = "LicenseIncluded"
  max_size_gb    = 4
  read_scale     = false
  sku_name       = "Basic"
  zone_redundant = false

  extended_auditing_policy {
    storage_endpoint                        = azurerm_storage_account.example.primary_blob_endpoint
    storage_account_access_key              = azurerm_storage_account.example.primary_access_key
    storage_account_access_key_is_secondary = true
    retention_in_days                       = 6
  }

// App Service configuration (Web Apps)
resource azurerm_app_service_plan "bddtest"{
    name = "bddtest-appserviceplan"
    location = azurerm_resource_group.bddtest.location
    resource_groupe_name = azurerm_resource_group.bddtest.name

    sku{
        tier = "Basic"
        size = "F1"
        name = "F1"
    }
}
 

Natürlich sind hier noch nich alle definitiven Werte eingetragen, sodass während der Build-Zeit diese Konfiguration erstellt wird. Diesen Punkt werde ich in Zukunft behandeln.

Erstellung der Ressourcen
Nun ist es an der Zeit, zu sehen ob die Konfiguration, die da entstanden ist, auch ohne Fehler angewendet werden kann.
Hierzu bin ich wie folgt vorgegangen:
1. Anmelden in Azure (Diese Methode habe ich gewählt, weil bei mir der Browser oder die Browser ein wenig gezickt haben.)

az login --use-device-code

Nach erfolgter Anmeldung, sind die Azure-Abos ersichtlich.

[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "YYYYYYYY",
    "id": "0000000-11111-2222-3333-555555555555",
    "isDefault": true,
    "managedByTenants": [],
    "name": "Pay-As-You-Go",
    "state": "Enabled",
    "tenantId": "ZZZZZZZ",
    "user": {
      "name": "hansmustter@windowslive.com",
      "type": "user"
    }
  },
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "XXXXXXX",
    "id": "0000000-11111-2222-3333-444444444444",
    "isDefault": false,
    "managedByTenants": [],
    "name": "Visual Studio Enterprise mit MSDN",
    "state": "Enabled",
    "tenantId": "XXXXXX",
    "user": {
      "name": "hansmuster@windowslive.com",
      "type": "user"
    }
  }
]

Nun, wenn man die Subscription wechseln will, muss man mit den Azure CLI-Werkzeugen folgenden Befehl ausführen:

az account list

[
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "YYYYYYYY",
    "id": "0000000-11111-2222-3333-555555555555",
    "isDefault": false,
    "managedByTenants": [],
    "name": "Pay-As-You-Go",
    "state": "Enabled",
    "tenantId": "ZZZZZZZ",
    "user": {
      "name": "hansmustter@windowslive.com",
      "type": "user"
    }
  },
  {
    "cloudName": "AzureCloud",
    "homeTenantId": "XXXXXXX",
    "id": "0000000-11111-2222-3333-444444444444",
    "isDefault": true,
    "managedByTenants": [],
    "name": "Visual Studio Enterprise mit MSDN",
    "state": "Enabled",
    "tenantId": "XXXXXX",
    "user": {
      "name": "hansmuster@windowslive.com",
      "type": "user"
    }
  }
]

das die Default Subscription geändert hat.

  1. Nun muss der Plan von Terraform initiiert werden.
.\terraform.exe plan -out .\myplan
terraform init
  1. Anschliessend muss
.\terraform.exe plan -out .\myplan

ausgeführt werden. Wobei der Out Parameter optional ist. Bei der Ausführung speichert Terraform mit diesem Parameter den Plan als Datei im aktuellen Verzeichnis oder in dem angegeben im out-Parameter, ab. Verlief alles nach Plan, so zeigt Terraform den Plan an. Die + Zeichen deuten darauf hin, dass diese Ressourcen erstellt werden. Der nachfolgende Plan ist der Übersicht halber symbolisch dargestellt. In Wirklichkeit ist dieser um einiges länger.

------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_app_service.bddtest will be created
  + resource "azurerm_app_service" "bddtest" {
      + app_service_plan_id            = (known after apply)
      + app_settings                   = {
          + "SOME_KEY" = "some-value"
        }
      + client_affinity_enabled        = false
      + client_cert_enabled            = false
      + default_site_hostname          = (known after apply)
      + enabled                        = true
      + https_only                     = false
      + id                             = (known after apply)
      + location                       = "switzerlandnorth"
      + name                           = "bddtest-dnug-bern"
      + outbound_ip_addresses          = (known after apply)
      + possible_outbound_ip_addresses = (known after apply)
      + resource_group_name            = "bddtest-dotnet-usergroup"
      + site_credential                = (known after apply)

      + auth_settings {
          + additional_login_params        = (known after apply)
          + allowed_external_redirect_urls = (known after apply)
          + default_provider               = (known after apply)
          + enabled                        = (known after apply)
          + issuer                         = (known after apply)
          + runtime_version                = (known after apply)
          + token_refresh_extension_hours  = (known after apply)
          + token_store_enabled            = (known after apply)
          + unauthenticated_client_action  = (known after apply)

          + active_directory {
              + allowed_audiences = (known after apply)
              + client_id         = (known after apply)
              + client_secret     = (sensitive value)
            }          
....

Plan: 7 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

This plan was saved to: .\MyPlan

To perform exactly these actions, run the following command to apply:
    terraform apply ".\\MyPlan"
  1. Nun wird der Plan mit
terraform apply

zur Ausführung gebracht.

  1. Eine kurze Rechenphase und der Plan der ausgeführt werden wird, wird angezeigt. Die Bestätigung mit „Yes“ lässt Terraform nun seine Arbeit verrichten.
terraform apply
  1. Sobald die Bestätigung mit „YES“ erfolgte, schreitet Terraform zur Tat. Die Fortschritte sind dann wie folgt:
terraform progress indikator
  1. Wenn alles richtig gemacht worden ist, dann zeigt Terraform den Status der Operation an.
terraform status

Hier noch die erstellten Ressourcen im Azure Portal.

azure ressourcen nach der terraform applizierung

Löschen der erstellten Ressourcen

Der Aufbau einer Umgebung ist das Eine. Das Abbauen das Andere. In der Dotnet User Group Bern, sollt diese Umgebung für die BDD (Behavior Driven Tests) hochgefahren und dann wieder abgebaut werden. Der Abbau geht dann wie nachfolgend beschrieben, sehr schnell.

  1. Mit dem Befehl
terraform.exe destroy

werden die Ressourcen dann abgebaut.

2. Nach einer kurzen Verarbeitungszeit auf dem ausführenden Client sieht man dann Ausführungsplan des oben bereits dargestellten Ausführungsplanes. Der Unterschied hier, bei jeder Resource ist nun ein vorangestellt, dass angibt das die Ressource gelöscht werden wird. Auch hier muss mit YES die Aktion bestätigt werden.

3. Sind alle Aktionen erfolgreich verlaufen so teilt terraform mit, dass die Ressourcen zerstört sind.

terraform destory

Auch im Azure Portal sind die Ressourcen nicht mehr zu finden.

azure nach dem abbau durch terraform destroy

Fazit

Mit Terrform ist es relativ einfach eine automatisierte Infrastruktur zu instantiieren und so den Prozess der Bereitstellung zu Beschleunigen. Wenn Dir dieser Artiekl gefallen hat, dann würde ich mich über ein Like freuen und nehme gerne Verbesserungsvorschläge an. Meine Reise mit Terraform ist noch nicht zu Ende und das hier gezeigt, einfache Beispiel, kann noch weiter verbessert werden. Weiter will ich zudem die Möglichkeiten erkunden, diese Lösung auch onPremise einzusetzen, da nicht in jeder Umgebung die Cloud als definierte Zielplattform gewünscht ist.

Weiterführende Links

Golo Roden: Was man über React wissen sollte

Was ist React? Kurz gefasst handelt es sich bei React um eine Bibliothek zur Entwicklung grafischer Oberflächen für Webanwendungen. Doch was gibt es darüber hinaus über React zu wissen?

Holger Schwichtenberg: Infonachmittag: Eine moderne User Experience (UX) für Ihre Software am 26. November 2020

Bei diesem Online-Event präsentieren User-Experience-Experten ihre Erfahrungen aus spannenden Softwareanwendungen von Versicherungen, Energieversorgern, dem Deutschen Notruf sowie aus dem militärischen Umfeld.

Golo Roden: Zwölf Regeln für Web- und Cloud-Anwendungen

Das Entwickeln skalierbarer und verlässlicher Anwendungen für Web und Cloud ist ein komplexes Thema, das eine gewisse Erfahrung fordert. Dennoch gibt es Leitplanken für den einfachen Einstieg, allen voran die Regeln der 12-Factor-Apps. Was hat es mit damit auf sich?

Holger Schwichtenberg: Über 80 Neuerungen in Entity Framework Core 5.0

Die am 10. November erschienene Version des OR-Mappers enthält zahlreiche Neuerungen.

Holger Schwichtenberg: .NET 5.0 erscheint am 10. November im Rahmen der .NET Conf

Microsoft wird morgen im Rahmen der Online-Konferenz ".NET Conf 2020" die fertige Version von .NET 5.0 veröffentlichen.

Golo Roden: Extreme Programming, Scrum, Kanban – was für wen?

Extreme Programming (XP), Scrum und Kanban sind die am weitesten verbreiteten agilen Methoden. Doch obwohl es sich bei allen drei um agile Methoden handelt, unterscheiden sie sich teilweise gravierend voneinander. Was sind die Gemeinsamkeiten, was die Unterschiede, und welche Methode eignet sich für wen?

Marco Scheel: Eigene Vorlagen für Microsoft Teams

Microsoft hat im Mai angekündigt, dass man in Kürze auf Microsoft definierten Templates bei der Anlage zurückgreifen kann und in Zukunft auch eigene Templates im Admin-Center erstellen kann. In der Vergangenheit brauchte man eine Teams Provisioning Lösung und konnte nicht auf die eingebauten Dialoge zurückgreifen. Hier ein Beispiel, wie man über ein Site Design ein Microsoft Flow startet, um mit Teams zu interagieren.

In meinem Lab-Tenant ist nun endlich die Erstellung eigener Templates angekommen. Ich zeige euch, was es mit den Templates von Microsoft auf sich hat und was ihr mit den eigenen Templates erreichen könnt.

image

Microsoft Templates

Microsoft bietet aktuell 13 Templates an, die man im Tenant auswählen kann. Die Templates werden mit einer “Industry” Information versehen. Auch wenn euer Unternehmen nicht aus dem Bereich stammt, sind die Templates trotzdem sinnvoll. Hier findet ihr die Dokumentation, was das einzelne Template ausmacht:

Get started with Teams templates using Microsoft Graph - Teams template capabilities

Aus der User-Sicht startet ihr über den normalen Dialog zum Erstellen eines Teams. Habt ihr die Self-Service Creation abgeschaltet, dann sollten wir mal ein ernstes Wort reden. Wenn ihr alles “Richtig” gemacht habt, dann sieht der User folgendes:

image

Auf dieser Seite kann der Benutzer sich über eine kurze Beschreibung über das Template informieren. Ist die Entscheidung gefallen, dann kommen beim Klick weitere Details zum Template:

image

Ab hier geht es wie gewohnt weiter. Klassifizierung + Privacy auswählen und den Namen für das Team festlegen:

image

Jetzt kommen die ersten Unterschiede. Ohne Template ist das Team in Sekunden erstellt und ich kann weitere Benutzer auswählen. Bei der Verwendung einer Vorlage dauert das Erstellen deutlich länger. Es geht jetzt nicht um Stunden aber der Prozess ist ab jetzt asynchron. Der Benutzer sieht folgende Meldung, welche in meinem Test sich auch nach Minuten nicht verändert hat. Das Schließen des Dialoges ist also nicht optional, wie angedeutet.

image

Das fertige Team sieht dann so aus:

image

In meinem Test hat das System am Ende immer “undefinded” an den gewählten Namen angehängt, aber als Owner kann man den Namen ja jederzeit ändern.

In der aktuellen Implementierung kann man keine der Microsoft Templates ausblenden. Es gibt auch keine Möglichkeit Templates an eine Zielgruppe zu verteilen. Alle Templates sind immer für alle Benutzer zu sehen. Ich bin gespannt, wie es hier weiter geht.

Templates administrieren

Als Microsoft Teams Administrator kann ich ab sofort eigene Templates erstellen und in den gezeigten Dialog integrieren. Die eigenen Templates werden immer vor den Microsoft Templates angezeigt. Hier könnt ihr die Microsoft Dokumentation einsehen:

Get started with Teams templates in the admin center

In der aktuelle Implementierung ist der Funktionsumfang recht überschaubar. Ihr könnt Kanäle vordefinieren, Tabs in die Kanäle hängen und generell Apps in das Team einfügen.

Im Admin Center gibt es im Bereich “Teams” einen neuen Navigationspunkt:

image

Hier kann man ein neues Template erstellen. Aktuell hat man drei Optionen:

image

Eigene Template definieren

Die erste Option “Create a custom team template” führt uns durch den folgenden Dialog. Name des Template, Beschreibung für den Endbenutzer und Hinweise für die Kollegen im Admin Team sind verpflichtend:

image

Auf der folgenden Seite kann ich jetzt einen Kanal anlegen und gleichzeitig auch Tabs (Apps) hinzufügen:

image

Sollte eine App nicht über einen Tab hinzugefügt werden, dann kann ich diese auch direkt in das Team einfügen:

image

Ihr könnt den erstellten Kanälen (z.b. dem Standard-Kanal) nachträglich Apps hinzufügen:

image

Hier seht ihr die fertige Definition des Templates:

image

Eigenes Template von einem bestehenden Template

Die zweite Option “Create a team template from an existing team template” schaltete vor die eigentliche Definition des Templates die Auswahl eines anderen (Microsoft oder eigenes) Template vor:

image

Dann geht es weiter wie bei der Definition eins leeren Template.

Eigenes Template von einem bestehendem Team

Die dritte Option “Create a template from an existing team” erlaubt es ein bestehendes Team auszuwählen:

image

Dann geht es weiter wie bei der Definition eins leeren Template.

In dieser Auswahl werden aber auch nur die Channels, Tabs und die Apps übernommen. Inhalte wie Chat, Tab-Konfigurationen oder Dateien kommen nicht mit.

Zusammenfassung

Es ist eine willkommene Option mit viel Potential für die Zukunft. Wenn ihr kein eigenes Teams Provisioning machen wollt, ist das die erste Möglichkeit euren Usern vorgefertigte Strukturen an die Hand zu geben. Es fehlen aber ganz essenzielle Dinge. Ich kann keine Dateien vorprovisionieren und zum Beispiel als Tab integrieren. Tabs können nicht mit Inhalten gefüllt werden. Ein “Website” Tab bekommt einen Namen, aber man kann nicht die URL hinterlegen.

Es ist gut zu wissen, dass man hier „out-of-the-box“ Funktionen hat, aber solange Microsoft folgende Punkte nicht adressiert, wird es eine Nischenlösung bleiben:

  • Teams Eigenschaften verändern (Moderation, etc.)
  • Erstellungsdialog muss schneller werden
  • Kein User/Group Targeting für Templates -> Heute sehen alle User alle Templates
  • Die Verwendung eines Templates kann nicht erzwungen werden, der User kann immer noch “From scratch” wählen und sein eigenes Ding machen
  • Microsoft Templates können nicht ausgeblendet werden
  • Keine Inhalte (Tabs, Dateien, …)

Holger Schwichtenberg: Zwangweises Herunterfahren einer Reihe von Windows-Systemen per PowerShell

Dieses PowerShell-Skript fährt eine Reihe von Windows-Rechnern herunter. Die Rechnernamen kann man im Skript hinterlegen oder aus einer Datei oder dem Active Directory auslesen.

Golo Roden: Node.js und Deno im Vergleich

Mit Node.js und Deno stehen inzwischen zwei Laufzeitumgebungen auf Basis von V8 zur Verfügung, die JavaScript (und TypeScript) ausführen können. Während Node.js seit über 10 Jahren existiert, ist Deno noch verhältnismäßig jung. Wie unterscheiden sich die beiden Plattformen, und was spricht für Node.js, was für Deno?

Marco Scheel: Microsoft Teams Recording mit Externen teilen

Microsoft arbeitet an einer neuen Version von Microsoft Stream. Microsoft Teams nutzte bis vor kurzem genau dieses Video-Backend für die Ablage der Meeting Recordings. Seit heute (01.11.2020) beginnt der Rollout für alle Tenants, es sei denn ihr habt per Meeting Policy ein Opt-Out für eure Benutzer gesetzt. Was das Recording in OneDrive/Teams bedeutet habe ich euch in folgendem Blogpost demonstriert.

Das Meeting Recording liegt also im SharePoint (für Channel Meetings) oder im OneDrive (für alle anderen Meetings). Ist damit der Blog bereits zu Ende? Teilen auf SharePoint kann doch jeder, oder? Hier gibt es die Doku von Microsoft. Natürlich kann ich die Datei einfach über einen neuen Sharing Link teilen. Wenn der externe Benutzer aber im Meeting-Chat auf das Recording klickt, dann gibt es folgenden Fehler, den wir aber “einfach” Lösen können.

image

Meeting Recordings richtig berechtigen

Microsoft nutzt die normalen Share-Features für das Teilen des Recordings für interne Benutzer. Wenn ihr jetzt einfach den Share-Dialog am Video nutzt, dann kann der externe über den Link aus diesem Sharing (wird normal per E-Mail verschickt) zugreifen. Versucht er aber vielleicht später über Teams und den Meeting-Chat zuzugreifen, dann kommt es wieder zum Fehler, da dort ein anderer Sharing-Link hinterlegt ist:

image

Jetzt zeige ich euch, wie man den Link im Meeting-Chat auch für Externe konfiguriert. Wenn ihr die Datei im SharePoint oder OneDrive geöffnet habt, dann könnt ihr über “Manage access” die aktuellen Sharing Links einsehen. Es gibt viele Möglichkeiten “Manage access” zu erreichen. In der Ordneransicht klickt ihr “…” auf der entsprechenden Videodatei und wählt dann “Manage access” aus:

image

Ihr seht die aktuell konfigurierten Freigaben:

image

Die Freigabe für die Anzeige der Datei (“View”-Berechtigung) könnt ihr über die “…” Option aufrufen:

image

Gebt die E-Mail des Gasts ein und bestätigt mit “Save”:

image

Wenn ich (als Gast der Besprechung) nun über den Meeting-Chat den Link aufrufe, dann bekomme ich keine Fehlermeldung beim Zugriff:

image

Zusammenfassung

Die meisten Benutzer werden einfach im SharePoint auf “Share” klicken. Der Zugriff für externe Benutzer ist ein tolles Feature und ich will nicht meckern. Sollten eure Benutzer über das Verhalten meckern, dann könnt ihr sie nun über den richtigen Weg aufklären und das Nutzererlebnis verbessern.

Code-Inside Blog: DllRegisterServer 0x80020009 Error

Last week I had a very strange issue and the solution was really “easy”, but took me a while.

Scenario

For our products we build Office COM Addins with a C++ based “Shim” that boots up our .NET code (e.g. something like this. As the nature of COM: It requires some pretty dumb registry entries to work and in theory our toolchain should “build” and automatically “register” the output.

Problem

The registration process just failed with a error message like that:

The module xxx.dll was loaded but the call to DllRegisterServer failed with error code 0x80020009

After some research you will find some very old stuff or only some general advises like in this Stackoverflow.com question, e.g. “run it as administrator”.

The solution

Luckily we had another project were we use the same approach and this worked without any issues. After comparing the files I notices some subtile differences: The file encoding was different!

In my failing project some C++ files were encoded with UTF8-BOM. I changed everything to UTF8 and after this change it worked.

My reaction:

(╯°□°)╯︵ ┻━┻

I’m not a C++ dev and I’m not even sure why some files had the wrong encoding in the first place. It “worked” - at least Visual Studio 2019 was able to build the stuff, but register it with “regsrv32” just failed.

I needed some hours to figure that out.

Hope this helps!

Marco Scheel: Microsoft Teams Recording jetzt in SharePoint statt Microsoft Stream

Auf der Ignite 2020 wurde angekündigt, dass man Microsoft Stream einstellen neu erfinden wird. Ich bin Feuer und Flamme für die Idee, wie ihr hier sehen könnt:

Den Microsoft Blogpost mit allen Details findet ihr hier. Heute wollen wir uns die Auswirkungen auf die Meeting Recordings in Microsoft Teams anschauen. In der “Vergangenheit” hatten wir folgende Probleme mit der Ablage in Microsoft Stream:

Mit dem neuen Microsoft Stream gehören diese Probleme der Vergangenheit an und es werden noch viele Funktionen in der nahen Zukunft ergänzt. Zum Start bekommen wir aber eine sehr rudimentäre Implementierung mit ihren eigenen Problemen. Wir schauen einmal auf die entsprechende Implementierung Stand Oktober 2020. Microsoft hat zur Ignite eine dedizierte Session zum Thema Besprechungsaufzeichnung erstellt in der ihr viele Details findet.

image

Im Meeting

Ich habe für euch ein Meeting dokumentiert und zeige wo die Unterschiede liegen. Im Microsoft Teams Client bleibt während der Besprechung alles beim Alten. Über die erweiterten Funktionen (…) kann jeder Moderator aus dem einladenden Unternehmen das Recording starten “Start recording”: image

Im Meeting sehen wir:

  • Luke (Meeting Organizer) - luke ät gkmm.org
  • Leia - leia ät gkmm.org
  • Rey - rey ät gkmm.org
  • Kylo - kylo ät gkmm.org
  • Marco (Gast) - marco.scheel ät glueckkanja.com

Die Benutzer werden wie üblich mit einem Banner über den Start der Aufnahme informiert: image

Wird die Aufzeichnung während des Meetings beendet, dann werden die Benutzer über das Speichern informiert: image

Die Aufzeichnung wird im Meeting Chat verlinkt. image

Recording ansehen

Hier kommt die erste Neuerung! Das Video ist deutlich schneller verfügbar. Microsoft Teams erzeugt das Video als MP4 in der Cloud und hat es in der Vergangenheit an Microsoft Steam übergeben. Stream hat dann die Azure Media Services bemüht, um das Video aufzubereiten und für ein adaptives Streaming auszuliefern. Simple gesagt: Stream hat das Video in verschiedenen Auflösungen gerechnet und kann nahtlos zwischen den Bitraten hin und her wechseln. Das neue Stream wirft einfach das MP4 in SharePoint (oder OneDrive for Business) und stellt es dann über einen einfachen HTML Player zur Verfügung. Es sollte klar werden, dass ohne die Integration der Azure Media Services in dieser ersten Version einige Funktionen entfallen:

  • Wiedergabegeschwindigkeit. Nicht jeder redet so schnell wie ich und dann kann man schon ein 60 Minuten Video auf 45 Minuten reduzieren, wenn man es in 1,5x wiedergibt.
  • Transkription (Sprache zu Text). Für ein Meeting Recording ziemlich relevant, um zum Beispiel im 2 Stunden Meeting den Moment zu finden, als es um Produkt X ging.
  • Bandbreiten- und Performanceabhängiges Adaptives Streaming (wechseln von 1.1 Mbps bis 58Kbps). Für ein Meeting Recording nicht besonders relevant.

Anfang 2020 hatten unsere Video Recordings in Stream noch alle Bitraten. Aktuell sind auch alle Stream Videos (Meeting Recording) nur in der Original 1.1 Mbps (1080p) Auflösung verfügbar und macht somit kein adaptives Streaming. Corona lässt grüßen?

Der folgende Screenshot zeigt Lukes Browser bei der Wiedergabe, nachdem er im Chat auf “Open in OneDrive” klickt. image

Genau so wird heute bereits jedes andere Video in SharePoint und OneDrive dargestellt. In der Zukunft wird der Content Typ für Video im SharePoint aufgewertet und die Darstellung, die Metadaten und der Lifecycle werden optimiert.

Für alle Meeting Teilnehmer aus der einladenden Organisation wird das Sharing der Datei automatisch eingerichtet: image

Teams vergibt immer zwei Berechtigungen für die Video Datei. Es wird unterschieden in Moderator und Teilnehmer. Moderatoren erhalten Edit Berechtigungen. Teilnehmer des Meetings erhalten nur View Berechtigungen.

Gäste werden nicht berücksichtig. Für mich als Gast endet der Versuch das Recording anzusehen so: image

In diesem Blogpost gehe ich auf den richtig Umgang mit Gästen und dem Recording ein.

Die Videodatei

Es bleibt zu beantworten, wo die Datei dann eigentlich liegt! Wenn man im Chat ein Video angeklickt, dann öffnet sich in Teams die Video Datei und wird abgespielt. Keine gute Idee, denn jede Interaktion mit Teams führt zum Abbruch der Wiedergabe und man muss von vorne anfangen. Also am besten gleich die “…” anklicken und “Open in OneDrive” auswählen: image

Die Datei liegt also im OneDrive und wird in einem Ordner mit dem Namen “Recordings” (bestimmt auch irgendwo “Aufzeichungen” oder “grabación”). OneDrive ist immer eine persönliche Ablage. Sie gehört einem Benutzer! Keine schöne Lösung. Also ein “Meet now” oder ein geplanter Termin landen im OneDrive eines Benutzers. Welcher Benutzer? Meeting Organizer oder der Benutzer der “Start recording” klickt. Es ist tatsächlich das OneDrive des Benutzers, der am schnellsten Klicken kann. Ich hätte den Meeting Organizer bevorzugt, da er auch der Besitzer des Termins ist. Hier hatte Stream mit seiner “neutralen” Ablageplattform klare Vorteile. Verlässt ein Benutzer das Unternehmen und sein OneDrive wird gelöscht, verschwinden alle Meeting Recordings mit seinem OneDrive! Microsoft hat angekündigt, dass es Mechanismen geben wird, welche automatisch “alte” Aufzeichnungen löschen wird. Der Speicherverbrauch auf SharePoint soll so reduziert werden. Eventuell kommen hier Retention Policies zum Einsatz. Diese Policies können nicht nur Dateien löschen, sondern auch für einen bestimmten Zeitraum garantiert Vorhalten. Es kann also sein, dass es hier auch eine Lösung für das Löschen eines OneDrives geben wird.

Meetings können aber auch alternativ in einem Teams Kanal geplant werden. Diese Channel-Meetings speichern ihr Recording in dem entsprechenden Ordner im Kanal. Wenn ich also im “General” Kanal aufzeichne, dann liegt die Datei hier “/sites/YOURTEAMSITE/Shared Documents/General/Recordings”. Ich bin semi-zufrieden. Die Ablage im Team löst die Probleme beim Zugriff und die Frage wem die Datei gehört. Solltet ihr aber zum Beispiel den Ordner General per OneDrive Client auf eurem Rechner syncen und immer alles Offline wollen… dann kommen jetzt auch größere Videodateien mit. Aber man kann nicht alles haben und ich hoffe, dass Microsoft in Zukunft diese Szenarien weiter optimiert.

Das Label der Schaltfläche im Meeting Chat heißt immer “Open in OneDrive” und ändert sich für ein Channel Meeting NICHT in “Open in SharePoint”. Auch hier gibt es die Chance auf Besserung in der Zukunft.

Schauen wir mal direkt auf die Datei. Hier ein Screenshot der erweiterten MP4 Eigenschaften: image

Die Aufzeichnung ist etwas unter 5 Minuten lang und verbraucht ca. 25 MB. Das Video besitzt eine FullHD (1080p) Auflösung. Wenn ich mir den Dateinamen anschaue, dann bin ich super happy. In Stream waren besonders die Titel der Channel Meetings oft Nichtssagend z.B. “Meeting in General”. So baut sich der Dateiname im neuen Stream auf:

  • Demo Luke and Leia-20201024_152149-Meeting Recording.mp4
    • TitelDesMeeting-yyyyMMdd_HHmmss-Meeting Recording
    • TitelDesMeeting = Demo Luke and Leia
    • yyyyMMdd_HHmmss = Start der Aufnahme (Lokale Zeit und nicht UTC), also der Moment in dem “Start recording” geklickt wird
  • Meeting in _Workplace_-20201023_100348-Meeting Recording.mp4
    • MeetingInKanal–yyyyMMdd_HHmmss-Meeting Recording
    • MeetingInKanal = Der Kanal heißt Workplace und in dem Fall wurde leider der Titel des Meetings nicht übernommen.
    • yyyyMMdd_HHmmss = Start der Aufnahme (Lokale Zeit und nicht UTC), also der Moment in dem “Start recording” geklickt wird

Einschalten im Tenant

Microsoft hat aktuelle Dokumentation wann es wie weiter geht. Aktuell kann ein Tenant Admin den Opt-In durchführen und in wenigen Stunden ist der Tenant bereit und alle neuen Meeting Recordings landen direkt im OneDrive/SharePoint. Wenn der Admin kein Opt-In oder Opt-Out macht, dann wird ab Mitte Q4 2020 die Funktion für den Tenant konfiguriert. Habt ihr für euren Tenant ein Opt-Out konfiguriert, dann ist trotzdem im Q1 2021 Schluss und das Recording auf OneDrive/SharePoint wird auch für euren Tenant umgestellt. Bedeutet, dass mit Start Q2 2021 alle Meeting Recordings nur noch im neuen Microsoft Stream (aka SharePoint) laden.

Für den Opt-In braucht ihr die Skype for Business PowerShell, um die entsprechende Meeting Policy zu setzen. Seit der Ignite 2020 sind die CommandLets aber auch in das Microsoft Teams PowerShell Modul integriert.

Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession
Import-PSSession $sfbSession
Set-CsTeamsMeetingPolicy -Identity Global -RecordingStorageMode "OneDriveForBusiness"

Da es eine Meeting Policy ist, kann man das Feature auch erstmal nur für einzelne Benutzer freischalten.

Für den Op-Out setzt ihr den Wert einfach auf “Stream”:

Set-CsTeamsMeetingPolicy -Identity Global -RecordingStorageMode "Stream"

Zusammenfassung

Microsoft Teams läutet eine neue Video Ära in Microsoft 365 ein. Die Meeting Recordings können ab sofort für all oder einzelne Benutzer nach SharePoint/OneDrive umgeleitet werden. Der Funktionsverlust ist angesichts der neuen Freiheit beim Teilen der Aufzeichnungen zu verschmerzen. Folgende Punkte sind also zu beachten:

  • Aufzeichnungen im Benutzer OneDrive können verloren gehen, wenn das OneDrive gelöscht wird (Mitarbeiter verlässt das Unternehmen)
  • Kein x-fache Wiedergabegeschwindigkeit
  • Keine adaptive Bandbreitenanpassung (aktuell auch in Stream nicht gegeben)
  • Keine Integration in die aktuelle Stream Mobile App
  • Wird eine Datei umbenannt, verschoben oder gelöscht, dann geht der Link im Meeting Chat kaputt
  • Das richtige Teilen mit Externen erfordert einige Klicks (Blogpost coming soon)
  • Keine Möglichkeit alle Meeting Recordings auf einen Blick zu sehen, man muss jedes Mal den Meeting Chat suchen

Holger Schwichtenberg: PowerShell 7: Ternärer Operator ? :

In PowerShell 7.0 hat Microsoft auch den ternären Operator ? : als Alternative zu if … else eingeführt.

Golo Roden: Node.js 15 – das ist alles neu

Node.js 15 ist erschienen – und Node.js 14 LTS erscheint nächste Woche. Was gibt es Neues, was hat sich geändert, und welche Besonderheiten sind beim Installieren zu beachten?

Golo Roden: Was ist Systemarchitektur?

Architektur ist die Beschäftigung mit der Frage, wie man Code strukturiert und orchestriert. Welche Architekturmöglichkeiten gibt es, und welche Vor- und Nachteile haben diese?

Marco Scheel: Teilnehmer in Microsoft Teams für immer stumm schalten

Wer kennt es nicht: Das Meeting startet und plötzlich “piiiiiiiiep-piiiiiep-piiep-piep” … der Projektleiter parkt rückwärts ein. Die Flexibilität in der Teilnahme an einem Teams Meeting ist “nahezu” unbegrenzt. Die Freiheitsgrade sind aber für viele neu und ungeübt. Der geübte Umgang mit dem Meeting Equipment ist noch in weiter Ferne. Im optimalen Fall setzen wir alle nur Microsoft Teams zertifizierte Geräte ein und Hardware + Software arbeiten in Harmonie. Wenn ich zu spät in ein Meeting komme, dann hoffentlich “on mute”. Die Realität sieht noch immer anders aus. Besonders in großen Meetings war es ein Problem, dass Teilnehmer jederzeit das Mikrofon öffnen konnten. Ein Microsoft Teams Live-Events waren oft keine Lösung, da die Interaktivität in einem späteren Moment fehlte. Durch die Einführung des Roadmap Item 66575 ist das Problem lösbar:

Prevent attendees from unmuting in Teams Meetings

Gives meeting organizers the ability to mute one to many meeting participants while removing the muted participants' ability to unmute themselves.

Hier der Screenshot zum Feature:

image

Teilnehmer (Attendee) vs Moderator(Presenter)

Microsoft Teams kennt in einem Meeting drei wesentliche Rollen. Für unser Szenario sind nur zwei Rollen interessant. Das Unternehmen kann vorgeben, wie strikt die Moderator-Rolle (Presenter) gehandhabt wird. Im Standard ist jeder Benutzer dieser Rolle zugeordnet und kann damit das Meeting unterstützen oder empfindlich stören. Im Meeting selbst kann jeder Moderator andere Benutzer zum Teilnehmer herabstufen. Teilnehmer können nicht präsentieren und keine Benutzer aus der Konferenz werfen. Für die genau Übersicht checkt diesen Link.

Welche Optionen das Unternehmen zur Vorgabe hat, findet ihr mit allen Details hier.

  • Jeder
  • Jeder aus dem Unternehmen
  • Nur der Organisator

Meeting planen

Wenn ihr ein Meeting fertig geplant habt, könnt ihr nachträglich in die Meeting Optionen schauen. In Skype for Business konnte man das schon direkt beim Planen, aber Microsoft hat es sich hier “einfach” gemacht und springt einfach im Browser auf diese Optionen. Im Beschreibungstext des Termins neben dem Teilnahme-Link, kann der Organisator auch aus allen anderen Programmen auf diese Optionen zugreifen:

image

Hier die Meeting Optionen im Browser:

image

Wer kann präsentieren? Hier ist der Unternhemnsstandard vorgegeben und ihr könnt den Wert nach euren Vorlieben anpassen.

image

Solltet ihr die Option “Specific people” auswählen, konnte ich in meinem Test nur eingeladenen Personen aus dem Unternehmen selbst auswählen. Je nachdem wer den Termin tatsächlich steuert, müsst ihr hier aufpassen, wenn ihr zum Beispiel durch einen externen Projektleiter unterstützt werden, der normalerweise das Meeting leitet.

Jetzt kommen wir zum eigentlichen Feature, wenn ihr schon zur Planung die Entscheidung treffen könnt, dann stellt ihr hier die Option “Allow attendees to unmute” aus.

image

Im Meeting

Während des Teams Meetings kann ein Moderator die Option zum “Stumm-schalten” der Teilnehmer direkt im Client bedienen. In der Teilnehmerliste kann man über das “…"-Menü die Benutzergruppe stumm schalten.

image

Für den Teilnehmer wird es im Client kurz signalisiert, dass Sie aktuell stumm sind. Ein “Unmute” über zum Beispiel zertifizierte Hardware-Button wird sofort wieder zurückgesetzt und das Headset (Dongle) zeigt weiterhin Mute (Rot) an.

image

Für mich als Teilnehmer ist das Symbol zum “Unmuten” ausgegraut und nicht bedienbar. Der Rest der Teilnehmer wird ebenfalls als deaktiviert angezeigt.

image

In meinem Test kann man übrigens sehen, dass noch nicht alles rund läuft. Für ein kurzen Moment war mein Mikrofon noch offen (wie beim berühmten Double-Mute) aber Teams hatte mich im Client schon stumm geschaltet. In dem Moment hat mich der Client dann drauf hingewiesen, dass ich noch mute bin, obwohl ich es ja nicht ändern kann :) Wird schon noch werden.

Ein Moderator kann jederzeit die Option wieder für alle entfernen oder einzelne Teilnehmer zum Moderator befördern.

Abschluss

Ich kannte die Funktion bisher nur von WebEx und wurde schon das eine oder andere Mal darauf angesprochen. Bisher war meine Antwort eine Kombination aus Live-Event und Teams Meeting im Anschluss. Durch diese neue Funktion wird es für alle viel einfacher. Ich würde trotzdem sparsam mit dem Setting umgehen, da die Tücke im Detail liegt. Aktuell können zum Beispiel nur die Web- und Desktop-Versionen damit umgehen. Auf einem Microsoft Teams Room System oder dem Mobile Clients (Android/iOS) gibt es die Funktion Stand Oktober 2020 noch nicht. Es kann also passieren, dass ein Meeting nicht stattfinden kann, weil die Teilnehmer nicht zu Wort kommen können. Wie so oft kann man durch gute User-Erziehung mehr erreichen als durch harte Regulation.

Microsoft hat wie so oft in letzter Zeit hervorragen Dokumentation zum Feature Online. Schaut also selbst nochmal rein.

Golo Roden: Einführung in React, Folge 9: Hands-on

Nach fünf Monaten, acht Folgen und mehr als sieben Stunden Laufzeit ist es an der Zeit für eine Retrospektive. Die neunte Folge der Einführung in React ist ein mehr als zweistündiges Hands-On, in dem alle bisher vorgestellten Konzepte miteinander kombiniert werden.

Marco Scheel: My blog has moved - From Tumblr to Hugo on GitHub Pages

My blog now has a new home. It is no longer hosted on Tumblr.com and it is now hosted on GitHub Pages. The main reason to get of Tumblr is the poor image handling. The overall experience was OK. I liked the editor and best of all it is all free including running on your own domain! Having my own name was a key driver. I was running my blog on my own v-server back in the days. I tried a lot of platforms (blogger.com, wordpress.com and prior Tumblr I ran on a “self” hosted WordPress instance). The only constant was and will be my RSS hosting. Believe it or not I’m still running Feedburner from Google. One service that is still not (yet?) killed by search giant (RIP Google Reader). With all the previous choices there was also on driving factor: I’m cheap, can I get it for free? Yes and it will stay 100% free for you and me!

Today is the day I switched to a static website! It is 2020 and the hipster way to go. So, what does it take to run a blog on a static site generator?

Main benefits:

  • Still free
  • I own my content 100%
  • Better image handling (high-res with zoom)
  • Better inline code handling and highlighting
  • Learning new stuff

HUGO

Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.

image

Why Hugo and not Jekyll? Because there are blogs out there that I’m reading, and I liked the idea of being one of them :) Who?

There is even content on Microsoft Docs on hosting Hugo on Azure Static Websites: https://docs.microsoft.com/en-us/azure/static-web-apps/publish-hugo

It was easy to start. Just follow the steps on the Getting started using choco installation for Windows users.

I’ve chosen the Fuji theme as a great staring point and integrated it as a git submodule. As mentioned in the docs I copied the settings into my config.toml and I was ready to go.

hugo new post/hugo/2020/10/my-blog-has-moved.md
hugo server -D

Open localhost:1313 in your browser of choice and check the result.

image

My tweaks

To get result in the picture above I needed some tweaks. Also, some other settings are notable if you are like me :)

The chosen theme is not very colorful and I really wanted a site image. I’m sure it is my missing knowledge about Hugo and theming but I ended up messing this the CSS to get a header image. I have put a classic CSS file in my “static/css” folder.

header {
    background-image: url(/bg-skate-2020.jpg);
    background-size: cover;
}

body header a {
    color: #ffffff;
    white-space: normal;
}

body header .title-sub{
    color: #ffffff;
    white-space: normal;
}

body .markdown-body strong{
    color: #000000;
}

To integrate this into your theme we use partials. To not mess with my theme (it is a submodule and controlled by the original author) I had to copy the “head.html” from my theme into “layouts/_partials” and I added the link to my CSS at the end of the file. While I’m in here I will also add the RSS tag to my FeedBurner account.

...
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/disqusjs@1.3/dist/disqusjs.css" />
{{ end }}
{{ partial "analytic-gtag.html" . }}
{{ partial "analytic-cfga.html" . }}

<link rel="stylesheet" href="/css/custom.css">
<link rel="alternate" type="application/rss+xml" href="http://feeds.marcoscheel.de/marcoscheel">

I also modified the Google Analytics integration in the same way. I copied the “analytic-gtag.html” file to my partials folder and added the attribute “anonymize_ip” to anonymize the IP address.

...
        dataLayer.push(arguments);
    }
    gtag('js', new Date());
    gtag('config', '{{ . }}', {'anonymize_ip': true});
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script>

To get a favicon I followed the instructions on my theme site doc.

By default, the RSS feed generated will include only a summary (I HATE THAT) and return all items. I’ve found this post about solving my RSS “problem”. This time we had to grab the content from the Hugo website and copy the file into “layouts/_default/rss.xml”. Switch from “.Summary” to “.Content” and switched the description of the RSS feed to my site description. Also, I configured the XML feed to only return 25 items.

...
<description>{{.Site.Params.subTitle}}</description>
...
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid>
<description>{{ .Content | html }}</description>

config.toml

rssLimit = 25

Content migration

I also need to take care about my old content living on Tumblr and if possible, on WordPress. It was kind of easy. I checked the migration article on the Hugo docs site.

Tumblr: https://gohugo.io/tools/migrations/#tumblr
All of the solutions require a Tumblr app registration, so I created on. To not mess with my fresh Windows install I enabled WSL2 and used the Ubuntu distro. This way I was able to clone the tumblr-importr repo and build my application. The important part was to place the GO binary into the right location. Otherwise the command was unknown. After that I was able to generate the needed files.

git clone https://github.com/carlmjohnson/tumblr-importr
cd tumblr-importr
go build
sudo cp tumblr-importr $GOPATH/bin
tumblr-importr -api-key 'MYAPIKEYHERE' -blog 'marcoscheel.de'

I copied the files into a subfolder named “tmblr” in my “content/post” folder. My main problem was that the content was not markdown. The files used HTML. I ended up opening all the blog posts on Tumblr in edit mode and switched to markdown mode and copied the source to the corresponding .md file. I only had 12 posts, so the work was doable and the result is clean. The main benefit of the conversion was that the front-matter attributes were pre-generated I did not have to recreate those (title, old URL as alias, tags, date, …)

date = 2019-08-02T19:41:30Z
title = "Manage Microsoft Teams membership with Azure AD Access Review"
slug = "manage-microsoft-teams-membership-with-azure-ad"
id = "186728523052"
aliases = [ "/post/186728523052/manage-microsoft-teams-membership-with-azure-ad" ]
tags = [ "Microsoft 365", "Azure AD", "Microsoft Teams"]

The Tumblr export generated an image mapping JSON. I used the JSON (converted to a CSV) to rewrite my images to the downloaded (still to small) version.

"OldURI":"NewURI"
"https://64.media.tumblr.com/023c5bd633c51521feede1808ee7fc20/eb22dd4fa3026290-d8/s540x810/36e4547d82122343bec6a09acf4075bb15eae1c1.png": "tmblr/6b/23/64d506172093d1d548651e196cf7.png"
$images = Import-Csv -Delimiter ":" -Path ".\image-rewrites.csv";

Get-ChildItem -Filter "*.md" -Recurse | ForEach-Object {
    $file = $_;
    $content = get-content -Path $file.FullName -Raw
    foreach ($image in $images) {
        $content = $content -replace $image.OldURI, $image.NewURI
    }
    Set-Content -Value $content -Path ($file.FullName)
}

WordPress: https://gohugo.io/tools/migrations/#wordpress
Once again, I used my handy WSL2 instance to not mess with not loved language. So a save route was to use the WordPress export feature and the repo exitwp-for-hugo. I cloned the repo and a few “sudo apt-get” later I was ready to run the python script. I placed my downloaded XML into the “wordpress-xml” folder. I ended up changing the exitwp.py file to ignore all tags and replace it with a single “xArchived”.

git clone https://github.com/wooni005/exitwp-for-hugo.git
cd exitwp-for-hugo
./exitwp.py

At the end, my “content/post” folder looks like to following.

image

Github

Now that the content is available on my local drive and I’m able to generate the static files. It is already a git repo so where to host the primary authority? So, the Hugo site with all config and logic will go to GitHub. There are only two choices for me. GitHub or Azure DevOps. Microsoft is owning both services. Private repos are free in both services. It looks like in the future Azure DevOps will not get all the love and that is why my website source code is hosted on GitHub: https://github.com/marcoscheel/marcoscheel-de

image

GitHub Pages

Next up is to generate the final HTML and put it out there on the internet. Generating the content is as easy as running this command.

image

Now we need to decide how to host the content. My first try was to setup a new Azure Pay-As-You-Go subscription with a 200$ starting budget for the first month and my personal credit card from here. Based on Andrew Connell blog I setup a storage account and enabled the static website. I could setup a custom domain for the blob store, but I created a Azure CDN (MS Standard) to optimize traffic and reduce potential cost. I also checked for Cloudflare CDN. All options allowed to have a custom domain and easy HTTPS with build in certificates. At the end it was my credit card and if something went really wrong (too much traffic due to non-paid internet fame?) I would be paying a life lesson with real money. I took the easy route instead. GitHub Pages to the rescue.

Websites for you and your projects. Hosted directly from your GitHub repository. Just edit, push, and your changes are live.

For every account GitHub is offering one GitHub Pages repository. I created the repository at: https://github.com/marcoscheel/marcoscheel.github.io

Normally the content will be server on the github.io domain, but through the settings we can add a CNAME to the site. To achieve this we need to put a file called “CNAME” into the root directory. For my Hugo site and the publish process I placed the file into the “static” folder so every time the site is generated the file will be copied to the root of the site. Once the CNAME is in place we configure the HTTPS redirect.

image

Custom domain. HTTPS. No credit card. Everything is good.

Publishing

In the future I’m looking forward to enable GitHub Actions to publish my site. For the moment I rely on my local environment pushing content from my Hugo site to the GitHub Pages repository. I integrated the GitHub Pages repo as a submodule and with the publish process I put the files into “public/docs”.

publishDir = "public/docs"

A quick “hugo” on the Windows Terminal and a fresh version is ready to be pushed into the right repo.

hugo
cd public
git add -A && git commit -m "Ready for go live"
git push

Holger Schwichtenberg: Erstes Buch zu C# 9.0 erschienen

Das Buch behandelt die wesentlichen Neuerungen in der neunten Sprachversion.

Golo Roden: Einführung in Docker, Folge 4: Images bauen

Ein wesentlicher Bestandteil der Arbeit mit Docker ist das Bauen eigener Images. Die vierte Folge der Einführung in Docker zeigt, wie das funktioniert.

Code-Inside Blog: How to share an Azure subscription in a team

We at Sevitec are moving more and more workloads for us or our customers to Azure.

So the basic question needs an answer:

How can a team share an Azure subscription?

Be aware: This approach works for us. There might be better options. If we do something stupid, just tell me in the comments or via email - help is appreciated.

Step 1: Create a directory

We have a “company directory” with a fully configured Azure Active Directory (incl. User sync between our OnPrem system, Office 365 licenses etc.).

Our rule of thumb is: We create for each product team a individual directory and all team members are invited in the new directory.

Keep in mind: A directory itself costs you nothing but might help you to keep things manageable.

Create a new tenant directory

Step 2: Create a group

This step might be optional, but all team members - except the “Administrator” - have the same rights and permissions in our company. To keep things simple, we created a group with all team members.

Put all invited users in a group

Step 3: Create a subscription

Now create a subscription. The typical “Pay-as-you-go” offer will work. Be aware that the user who creates the subscription is initially setup as the Administrator.

Create a subscription

Step 4: “Share” the subscription

This is the most important step:

You need to grant the individual users or the group (from step 2) the “Contributor” role for this subscription via the “Access control (IAM)”. The hard part is to understand how those “Role assignment” affect the subscription. I’m not even sure if the “Contributor” is the best fit, but it works for us.

Pick the correct role assignment

Summary

I’m not really sure why such a basic concept is labeled so poorly but you really need to pick the correct role assignment and the other person should be able to use the subscription.

Hope this helps!

Holger Schwichtenberg: Rückschau auf die BASTA! hybrid 2020

Die Herbst-BASTA! fand als Hybrid-Konferenz, das heißt vor Ort in Mainz und online im Internet, mit in Summe rund 400 Teilnehmer, davon 200 vor Ort, statt.

Golo Roden: Einführung in Docker, Folge 3: Container verwenden

Das Verwenden von Containern gehört zu den tagtäglichen Aufgaben im Umgang mit Docker. Dazu zählen unter anderem das Starten, Beenden und Aufräumen von Containern. Stefan Scherer zeigt in der dritten Folge der Einführung in Docker, wie das alles funktioniert.

Stefan Henneken: IEC 61131-3: abstrakter FB vs. Schnittstelle

Seit TwinCAT V3.1 Build 4024 können Funktionsblöcke, Methoden und Eigenschaften als abstract gekennzeichnet werden. Abstrakte FBs können nur als Basis-FB für die Vererbung genutzt werden. Ein direktes Instanziieren von abstrakten FBs ist nicht möglich. Somit haben abstrakte FBs eine gewisse Ähnlichkeit zu Schnittstellen. Es stellt sich nun die Frage, wann eine Schnittstelle und wann ein abstrakter FB zum Einsatz kommen sollte.

Eine sehr gute Beschreibung zu abstract liefert der Post The ABSTRACT keyword aus dem Blog PLCCoder.com oder das Beckhoff Information System. Deshalb soll das Wichtigste nur kurz wiederholt werden.

abstrakte Methoden

METHOD PUBLIC ABSTRACT DoSomething : LREAL
  • bestehen ausschließlich aus der Deklaration und enthalten keine Implementierung. Der Methodenrumpf ist leer.
  • können public, protected oder internal sein. Der Zugriffsmodifizierer private ist nicht erlaubt.
  • können nicht zusätzlich als final deklariert werden.

abstrakte Eigenschaften

PROPERTY PUBLIC ABSTRACT nAnyValue : UINT
  • können Getter oder Setter oder beides enthalten.
  • Getter und Setter bestehen ausschließlich aus der Deklaration und enthalten keine Implementierung.
  • können public, protected oder internal sein. Der Zugriffsmodifizierer private ist nicht erlaubt.
  • können nicht zusätzlich als final deklariert werden.

abstrakte Funktionsblöcke

FUNCTION_BLOCK PUBLIC ABSTRACT FB_Foo
  • Sobald eine Methode oder eine Eigenschaft mit abstract deklariert wurde, muss auch der Funktionsblock mit abstract deklariert werden.
  • Von abstrakten FBs können keine Instanzen angelegt werden. Abstrakte FBs können nur bei der Vererbung als Basis-FB verwendet werden.
  • Alle abstrakte Methoden und alle abstrakte Eigenschaften müssen überschrieben werden, damit ein konkreter FB entsteht. Aus der abstrakten Methode oder einer abstrakten Eigenschaft wird durch das Überschreiben eine konkrete Methode oder eine konkrete Eigenschaft.
  • Abstrakte Funktionsblöcke können zusätzlich konkrete Methoden und/oder konkrete Eigenschaften enthalten.
  • Werden bei der Vererbung nicht alle abstrakte Methoden oder nicht alle abstrakte Eigenschaften überschrieben, so kann der erbende FB auch wieder nur ein abstrakter FB sein (schrittweise Konkretisierung).
  • Zeiger oder Referenzen von Typ eines abstrakten FBs sind erlaubt. Diese können aber auf konkrete FBs referenzieren und somit deren Methoden oder Eigenschaften aufrufen (Polymorphismus).

Unterschiede abstrakter FB und Schnittstelle

Besteht ein Funktionsblock ausschließlich aus abstrakten Methoden und abstrakten Eigenschaften, so enthält dieser Funktionsblock keinerlei Implementierungen und hat dadurch eine gewisse Ähnlichkeit mit Schnittstellen. Im Detail gibt es allerdings einige Besonderheiten zu beachten.

Schnittstelleabstrakter FB
unterstützt Mehrfachvererbung+
kann lokale Variablen enthalten+
kann konkrete Methoden enthalten+
kann konkrete Eigenschaften enthalten+
unterstützt neben public noch weitere Zugriffsmodifizierer+
Verwendung bei Arrays+nur über POINTER

Durch die Tabelle kann der Eindruck entstehen, dass Schnittstellen nahezu komplett durch abstrakte FBs austauschbar sind. Allerdings bieten Schnittstellen eine größere Flexibilität durch die Möglichkeit, in unterschiedlichen Vererbungshierarchien verwendet zu werden. In dem Post IEC 61131-3: Objektkomposition mit Hilfe von Interfaces wird hierzu ein Beispiel gezeigt.

Als Entwickler stellt sich somit die Frage, wann eine Schnittstelle und wann ein abstrakter FB genutzt werden sollte. Die einfache Antwort lautet: am besten beides gleichzeitig. Hierdurch steht eine Standardimplementierung im abstrakten Basis-FB zur Verfügung, wodurch das Ableiten erleichtert wird. Jedem Entwickler bleibt aber die Freiheit erhalten, die Schnittstelle direkt zu implementieren.

Beispiel

Für die Datenverwaltung von Angestellten sind Funktionsblöcke zu entwerfen. Hierbei wird unterschieden zwischen Festangestellten (FB_FullTimeEmployee) und Vertragsmitarbeiter (FB_ContractEmployee). Jeder Mitarbeiter wird durch seinen Vornamen (sFirstName), Nachnamen (sLastName) und der Personalnummer (nPersonnelNumber) identifiziert. Hierzu werden entsprechende Eigenschaften bereitgestellt. Außerdem wird eine Methode benötigt, die den vollständigen Namen inklusive Personalnummer als formatierten String ausgibt (GetFullName()). Die Berechnung des Monatseinkommens erfolgt durch die Methode GetMonthlySalary().

Die Unterschiede beider Funktionsblöcke bestehen in der Berechnung des Monatseinkommens. Während der Festangestellte ein Jahreseinkommen (nAnnualSalary) bezieht, ergibt sich das Monatseinkommen des Vertragsmitarbeiters aus dem Stundenlohn (nHourlyPay) und der Monatsarbeitszeit (nMonthlyHours). Somit besitzen die beiden Funktionsblöcke für die Berechnung des Monatseinkommens unterschiedliche Eigenschaften. Die Methode GetMonthlySalary() ist in beiden Funktionsblöcken enthalten, unterscheidet sich aber in der Implementierung.

1. Lösungsansatz: abstrakter FB

Da beide FBs etliche Gemeinsamkeiten haben, liegt es nahe einen Basis-FB (FB_Employee) zu erstellen. Dieser Basis-FB enthält alle Methoden und Eigenschaften, die in beiden FBs enthalten sind. Da sich aber die Methoden GetMonthlySalary() in der Implementierung unterscheiden, wird diese in FB_Employee als abstract gekennzeichnet. Dadurch müssen alle FBs, die von diesen Basis-FB erben, GetMonthlySalary() überschreiben.

(abstrakte Elemente werden in kursiver Schriftart dargestellt)

Beispiel 1 (TwinCAT 3.1.4024) auf GitHub

Nachteile

Der Lösungsansatz sieht auf dem ersten Blick sehr solide aus. Wie aber weiter Oben schon erwähnt, kann der Einsatz von Vererbung auch Nachteile mit sich ziehen. Besonders dann, wenn FB_Employee Teil einer Vererbungskette ist. Alles was FB_Employee über diese Kette erbt, wird auch an FB_FullTimeEmployee und FB_ContractEmployee vererbt. Kommt FB_Employee in einem anderen Zusammenhang zum Einsatz, so kann eine umfangreiche Vererbungs-Hierarchie zu weiteren Problemen führen.

Auch gibt es Einschränkungen bei dem Versuch, alle Instanzen in einem Array als Referenzen abzulegen. Folgende Deklaration wird vom Compiler nicht zugelassen:

aEmployees : ARRAY [1..2] OF REFERENCE TO FB_Employee; // error

Statt Referenzen müssen Zeiger verwendet werden:

aEmployees : ARRAY [1..2] OF POINTER TO FB_Employee;

Allerdings ist bei der Verwendung von Zeigern einiges zu beachten (z.B. beim Online-Change). Aus diesem Grund versuche ich Zeiger so weit wie möglich zu vermeiden.

Vorteile

Es ist zwar nicht möglich, direkt eine Instanz eines abstrakten FB anzulegen, allerdings kann per Referenz auf die Methoden und Eigenschaften eines abstrakten FB zugegriffen werden.

VAR
  fbFullTimeEmployee :  FB_FullTimeEmployee;
  refEmployee        :  EFERENCE TO FB_Employee;
  sFullName          :  STRING;
END_VAR
refEmployee REF= fbFullTimeEmployee;
sFullName := refEmployee.GetFullName();

Auch kann es ein Vorteil sein, dass die Methode GetFullName() und die Eigenschaften sFirstName, sLastName und nPersonnelNumber im abstrakten Basis-FB schon vollständig implementiert und dort nicht als abstract deklariert wurden. Ein Überschreiben dieser Elemente in den abgeleiteten FBs ist nicht mehr notwendig. Soll z.B. die Formatierung für den Namen angepasst werden, so ist dieses nur an einer Stelle durchzuführen.

2. Lösungsansatz: Schnittstelle

Ein Ansatz mit Schnittstellen ähnelt sehr stark der vorherigen Variante. Die Schnittstelle enthält alle Methoden und Eigenschaften, die bei beiden FBs (FB_FullTimeEmployee und FB_ContractEmployee) gleich sind.

Beispiel 2 (TwinCAT 3.1.4024) auf GitHub

Nachteile

Dadurch das FB_FullTimeEmployee und FB_ContractEmployee die Schnittstelle I_Employee implementieren, muss jeder FB alle Methoden und alle Eigenschaften aus der Schnittstelle auch enthalten. Das betrifft auch die Methode GetFullName(), die in beiden Fällen die gleiche Berechnung durchführt.

Wurde ein Schnittstelle veröffentlich (z.B. durch eine Bibliothek) und in verschiedenen Projekten eingesetzt, so sind Änderungen an dieser Schnittstelle nicht mehr möglich. Wird eine Methode oder eine Eigenschaft hinzugefügt, so müssen auch alle Funktionsblöcke angepasst werden, die diese Schnittstelle implementieren. Bei der Vererbung von FBs ist dieses nicht notwendig. Wird ein Basis-FB erweitert, so müssen alle FBs die davon erben nicht verändert werden. Außer, die neuen Methoden oder Eigenschaften sind abstrakt.

Tipp: Kommt es doch vor, dass man eine Schnittstelle später anpassen muss, so kann man eine neue Schnittstelle anlegen. Diese erbt von der ursprünglichen Schnittstelle und wird um die notwendigen Elemente erweitert.

Vorteile

Funktionsblöcke können mehrere Schnittstellen implementieren. Schnittstellen sind dadurch in vielen Fällen flexibler einsetzbar.

Bei einem Funktionsblock kann zur Laufzeit per __QUERYINTERFACE() eine bestimmte Schnittstelle abgefragt werden. Wurde dieses implementiert, so ist über diese Schnittstelle ein Zugriff auf den FB möglich. Dieses macht den Einsatz von Schnittstellen sehr flexibel.

Ist die Implementierung einer bestimmten Schnittstelle bekannt, so kann der Zugriff über die Schnittstelle auch direkt erfolgen.

VAR
  fbFullTimeEmployee :  FB_FullTimeEmployee;
  ipEmployee         :  I_Employee;
  sFullName          :  STRING;
END_VAR
ipEmployee := fbFullTimeEmployee;
sFullName := ipEmployee.GetFullName();

Auch können Schnittstellen als Datentyp für ein Array verwendet werden. Alle FBs, welche die Schnittstelle I_Employee implementieren, können zu dem folgenden Array hinzugefügt werden.

aEmployees : ARRAY [1..2] OF I_Employee;

3. Lösungsansatz: Kombination abstrakter FB und Schnittstelle

Warum nicht beide Ansätze miteinander kombinieren und somit von den Vorteilen beider Varianten profitieren?

(abstrakte Elemente werden in kursiver Schriftart dargestellt)

Beispiel 3 (TwinCAT 3.1.4024) auf GitHub

Bei der Kombination der beiden Ansätze wird zunächst die Schnittstelle zur Verfügung gestellt. Anschließend wird die Verwendung der Schnittstelle durch den abstrakten Funktionsblock FB_Employee vereinfacht. Gleiche Implementierungen von gemeinsamen Methoden können in dem abstrakten FB bereitgestellt werden. Eine mehrfache Implementierung ist nicht notwendig. Kommen neue FBs hinzu, können diese auch direkt die Schnittstelle I_Employee nutzen.

Der Aufwand für die Umsetzung ist erstmal etwas höher als bei den beiden vorherigen Varianten. Aber gerade bei Bibliotheken, die von mehreren Programmierern eingesetzt und über Jahre weiterentwickelt werden, kann sich dieser Mehraufwand lohnen.

  • Wenn der Anwender keine eigene Instanz des FBs anlegen soll (weil dieses nicht sinnvoll erscheint), dann sind abstrakte FBs oder Schnittstellen hilfreich.
  • Wenn man die Möglichkeit haben will, in mehr als einen Basistyp zu verallgemeinern, dann sollte eine Schnittstelle zum Einsatz kommen.
  • Wenn ein FB ohne die Implementierung der Methoden oder Eigenschaften vereinbart werden kann, dann sollte man eine Schnittstelle dem abstrakten FB vorziehen.

Daniel Schädler: Desired State Configuration und Group Managed Service Accounts

Ausgangslage

In meinem Unternehmen setzen wir IBM Urban Code Deploy ein für die Provisionierung der Server. Nun da dies einen manuellen Mehraufwand bedeutet muss dieses Vorgehen effizienter gestaltet werden. Aus diesem Grund habe ich mit für die Konfiguration mittels Desired State Configuration (DSC) entschieden.

Ziel

Durchführung

Für die Durchführung orientieren wir uns an den gesteckten Zielen.

Ist JAVA instaliert?

Hat man JAVA installiert, so wird dies meistens in ein Verzeichnis wie JDK oder JAVA gemacht und im Pfad wird das entsprechende angegeben. Die Konfiguration wird nicht gestartet, sollte JAVA nicht installiert sein. Dies kann man auf einfache Art und Weise überprüfen.

if(($env:Path.Split(';') | Where-Object { Where-Object { $_ -like "*JDK*" -or $_ -like "*JAVA*" } ))

Nun gut, wenn alles korrekt verläuft dann kann die Konfiguration beginnen.

Installation des Agenten

Der Agent wird mittels XCOPY Installation auf dem System installiert. Das hat den Vorteil, dass der Agent bereits als Vorlage "gezipped" zur Verfügung steht und dann noch an die richtige Stelle kopiert wird. Um diesen Vorgang auszuführen braucht es die Resource Archive, die bereits standardmässig von Windows zur Verfügung steht. Die Resource sieht wie folgt aus:

            Archive UrbanCodeBinaries
            {
                Destination = "C:\Program Files"
                Ensure =  "Present"
                Path = $agentSource
                Force = $true                
            }

Die Destination ist das Ziel in welche die binären Dateien extrahiert werden. Der Path wird durch das Aufrufen des Konfigurationsscriptes mitgegeben.

Konfiguration des Dienstes für den Agenten

Für die Konfiguration des Agenten ist die Service Resource verwendet worden, die wie folgt konfiguriert wird.


            # Preconfigure the properties for the service
            $agentName = ("ucd-agent-{0}" -f $env:COMPUTERNAME)
            $servicePathArguments = "//RS//{0}" -f $agentName
            $servicePath = Join-Path -Path $env:ProgramFiles -ChildPath "ibm-ucd-agent\native\x64\winservice.exe"

            Service UrbanCodeAgentService
            {
                Ensure = "Present"
                Name = $agentName
                StartupType = "Manual"
                Path = ("`"{0}`" {1}" -f $servicePath, $servicePathArguments)
                Description =  "IBM Urban Code Agent"                
                DisplayName = $agentName  
                State = "Stopped"                             
            }

Vor der Ressource sind die notwendigen Parameter vorkonfiguriert worden, da dieser JAVA Agent über einen Windows-Dienst Wrappter mit Argumenten gestartet wird. Der Dienst ist nun konfiguriert nur noch nicht so wie gewünscht.

Konfiguration des Dienstes für den Agent

Die Dienst Resource lässt nur zu einen Benutzer-Account mit Passwort zu verknüpfen. Da kein Object PSCredential Objekt ohne Passwort erstellt werden kann und das Passwort für den Group Managed Service Account unbekannt ist, muss man sich mittels WMI den Dienst wie gewünscht konfigurieren. Dies geschieht mittels der Script Resource

            # Third Set the Service with a script Resource
            Script ServiceScript{
                DependsOn = "[Service]UrbanCodeAgentService"
                GetScript = {                    
                    return Get-WmiObject -Class win32_Service | Where-Object -Property Name -like ("*{0}*" -f $using:agentName)
                }
                TestScript = {                    
                    $service = [scriptblock]::Create($GetScript).Invoke()                    
                    if($service){
                        return ($service.StartName -eq $using:groupManagedServiceAccount)
                    }
                    return $false
                }
                SetScript = {                    
                    $service = [scriptblock]::Create($GetScript).Invoke()
                    if($service){
                        $service.Change($null, $null, $null, $null, $null, $null, $using:groupManagedServiceAccount, $null, $null, $null, $null)
                    }                    
                }
            }

Wichtig an dieser Stelle ist zu erwähnen, dass diese Script-Resource mit dem DependsOn versehen ist. Das bedeutet, dass diese Ressource erst durchgeführt wird, wenn die angegeben Ressource erfolgreich appliziert werden konnte.

Hier noch das ganze Script

param([Parameter(Mandatory=$true,HelpMessage="The full path to the template ucd agent zip")]
      [ValidateNotNullOrEmpty()]
      [string]$agentSource="C:\Temp\ibm-ucd-agent.zip",
      [Parameter(Mandatory=$true,HelpMessage="The Group Managed Account that is used as service account.")]
      [ValidateNotNullOrEmpty()]
      [string]$groupManagedServiceAccount="IFC1\srvgp-ucd-r$"
      )

Configuration UrbanCodeAgentConfiguration{

    Import-DscResource -ModuleName PSDesiredStateConfiguration 

    if(($env:Path.Split(';') | Where-Object { $_ -like "*JDK*" -or $_ -like "*JAVA*" } )){
        Node $env:COMPUTERNAME
        {            
            # First Extract the service binaries to the Destination
            Archive UrbanCodeBinaries
            {
                Destination = "C:\Program Files"
                Ensure =  "Present"
                Path = $agentSource
                Force = $true                
            }

            # Preconfigure the properties for the service
            $agentName = ("ucd-agent-{0}" -f $env:COMPUTERNAME)
            $servicePathArguments = "//RS//{0}" -f $agentName
            $servicePath = Join-Path -Path $env:ProgramFiles -ChildPath "ibm-ucd-agent\native\x64\winservice.exe"

            # Second configure the service
            Service UrbanCodeAgentService
            {
                Ensure = "Present"
                Name = $agentName
                StartupType = "Manual"
                Path = ("`"{0}`" {1}" -f $servicePath, $servicePathArguments)
                Description =  "IBM Urban Code Agent"                
                DisplayName = $agentName  
                State = "Stopped"                             
            }  
            
            # Third Set the Service with a script Resource
            Script ServiceScript{
                DependsOn = "[Service]UrbanCodeAgentService"
                GetScript = {                    
                    return Get-WmiObject -Class win32_Service | Where-Object -Property Name -like ("*{0}*" -f $using:agentName)
                }
                TestScript = {                    
                    $service = [scriptblock]::Create($GetScript).Invoke()                    
                    if($service){
                        return ($service.StartName -eq $using:groupManagedServiceAccount)
                    }
                    return $false
                }
                SetScript = {                    
                    $service = [scriptblock]::Create($GetScript).Invoke()
                    if($service){
                        $service.Change($null, $null, $null, $null, $null, $null, $using:groupManagedServiceAccount, $null, $null, $null, $null)
                    }                    
                }
            }
        }        
    }else {
        Write-Host "Java is not installed"
    }
}

UrbanCodeAgentConfiguration

Eine Bemerkung zum Node: Started man die Desired State Configuration, so ist eine MOF-Datei das Resultat. Will man diese archivieren, so würde diese standardmässig "localhost" heissen, was nicht hilfreich wäre. Aus diesem Grund verwende ich immer die Variable $env:COMPUTERNAME um so eine sprechende MOF-Datei zu erhalten.

Fazit

Nicht alles was bereits standardmässig zur Verüfung steht, kann gleich für jeden Anwendungsfall verwendet werden. Mit der Script-Ressource ist es möglich eine gewisse Flexibilität gegeben und man kann auch ohne dedizierte Scripts Aktionen ausführen, die von keiner Ressource bereitgestellt werden. Für Fragen und Anregungen bin ich offen und wenn Dir der Artikel gefallen hat, dann freue ich mich über ein like

Golo Roden: Einführung in React, Folge 8: Fortgeschrittenes JSX

Eines der Kernkonzepte von React ist die JavaScript-Spracherweiterung JSX, weshalb es wichtig ist, sich auch mit deren fortgeschrittenen Konzepten zu beschäftigen.

Golo Roden: Einführung in Docker, Folge 2: Docker installieren

Bevor man Docker verwenden kann, muss man es zunächst installieren und konfigurieren. Die zweite Folge der Einführung in Docker zeigt, wie das funktioniert.

Daniel Schädler: Erstellen eines Windows Dienstes mit Desired State Configuration

In diesem Blog-Post soll gezeigt werden, wie man einfach einen Dienst mittels DSC (Desired State Configuration) installiert und wieder deinstalliert. Die meisten Beispiele zeigen nur kleine Schnipsel an Code, sodass kein Praxisnahes Beispiel herangezogen werden kann.

Ziel

  • Die Installation eines Windows Dienstes mittels DSC auf einem Windows Server 2019 Core

Durchführung

Um die Voraussetzungen zu erfüllen, um DSC verwenden zu können, kann hier nachgelesen werden.

Schritte zur Durchführung

  1. Die Quelldateien des Dienstes müssen von einem Quellverzeichnis in das Zielverzeichnis des Dienstes kopiert werden.
  2. Der Dienst muss dann mittels DSC installiert werden können.

Für diese Operation sind die zwei Windows Resourcen notwendig, die bereits fixfertig vom Betriebssystem geliefert werden.

Das komplette Script nachfolgend aufgeführt:

param([Parameter(Mandatory=$true)][string]$source,
      [Parameter(Mandatory=$true)][string]$destination,
      [Parameter(Mandatory=$true)][string]$servicename,
      [Parameter(Mandatory=$true)][string]$pathtoServiceExecutable)

Configuration ServiceDemo
{

    Import-DscResource -ModuleName PSDesiredStateConfiguration    

    Node $env:COMPUTERNAME
    {    
        File ServiceDirectoryCopy
        {
            Ensure = "Present"
            Type = "Directory"
            Recurse = $true
            SourcePath = $source
            DestinationPath = $destination
        }

        Service DemoService
        {
            Ensure = "Present"
             Name = $servicename
             StartupType = "Manual"
             State = "Stopped"
             Path = $pathtoServiceExecutable
        }
    }
}

Die Variable $env:COMPUTERNAME wird verwendet, damit die generierte MOF-Datei den Namen des Servers aufweist und die Konfiguration so zu anderen unterschieden werden kann.

Die ServiceDirectoryCopy Aktion führt das Kopieren in rekursiver Form der Dienst Dateien aus.

Die Operation DemoService konfiguriert und installiert den Dienst.

Nun muss mittels Aufruf der Scriptes und den Parametern die MOF-Datei erzeugt werden. In diesem Beispiel sieht der Aufruf wie folgt aus:

.\CreateService.ps1 -source "C:\_install\DemoService" -destination "C:\Program Files\DemoService" -servicename "DemoService" -pathtoServiceExecutable "C:\Program Files\DemoService\DemoService.exe"

Ist alles korrekt gelaufen, so sieht man das nachfolgende Resultat:

Erzeugte MOF-Datei
Die Erezugte MOF-Datei

Der Inhalt dieser Datei sieht dann so aus:

/*
@TargetNode='WIN-MFVO0VQ8PB7'
@GeneratedBy=Administrator
@GenerationDate=09/08/2020 12:07:47
@GenerationHost=WIN-MFVO0VQ8PB7
*/

instance of MSFT_FileDirectoryConfiguration as $MSFT_FileDirectoryConfiguration1ref
{
ResourceID = "[File]ServiceDirectoryCopy";
 Type = "Directory";
 Ensure = "Present";
 DestinationPath = "C:\\Program Files\\DemoService";
 ModuleName = "PSDesiredStateConfiguration";
 SourceInfo = "C:\\Users\\Administrator\\Documents\\DemoServiceConfiguration.ps1::14::9::File";
 Recurse = True;
 SourcePath = "C:\\_install\\DemoService\\";

ModuleVersion = "1.0";

 ConfigurationName = "ServiceDemo";

};
instance of MSFT_ServiceResource as $MSFT_ServiceResource1ref
{
ResourceID = "[Service]DemoService";
 State = "Stopped";
 SourceInfo = "C:\\Users\\Administrator\\Documents\\DemoServiceConfiguration.ps1::23::9::Service";
 Name = "DemoService";
 StartupType = "Manual";
 ModuleName = "PSDesiredStateConfiguration";
 Path = "C:\\Program Files\\DemoService\\notepad.exe";

ModuleVersion = "1.0";

 ConfigurationName = "ServiceDemo";

};
instance of OMI_ConfigurationDocument


                    {
 Version="2.0.0";
 

                        MinimumCompatibleVersion = "1.0.0"; 

                        CompatibleVersionAdditionalProperties= {"Omi_BaseResource:ConfigurationName"}; 

                        Author="Administrator"; 

                        GenerationDate="09/08/2020 12:07:47"; 

                        GenerationHost="WIN-MFVO0VQ8PB7";

                        Name="ServiceDemo";

                    };

Nun muss die Konfiguration appliziert werden. Dies geschieht mit dem folgenden Befehl:

Start-DscConfiguration .\ServiceDemo

Das Ausführen erzeugt einen Powershell Job der ausgeführt wird.

Ausführung der Konfiguration

Nun kann überprüft werden, ob die Konfiguration unseren Wünschen entspricht.

Get-Service *Demo*

liefert dann das Ergebnis, das erwartet wird.

Installierter Beispiel-Dienst

Anmerkung: Zum Demonstrationszweck dient bei mir notepad.exe als Demo-Dienst. Fragen wir die Dienst-Details mittels

Get-CimInstance -Class Win32_Service -Property * | Where-Object -Property Name -like "*Demo*"

so sehen wir alle konfigurierten Werte, dieses Dienstes und sehen, dass die Konfiguration angewendet worden ist.

Dienst Details

Fazit

Mit einfachen Mitteln lassen sich so ohne Komplexe Scripts gewünschte Zustände eines Systems festlegen und die Konfiguration ist einfacher zu lesen und zu interpretieren. Ein weiterer Vorteil liegt in der Idempotenz. Ein Nachteil dieser Methode ist, dass die Anmeldeinformationen nicht mitgegeben werden können und diese anschliessend konfiguriert werden müssen. Eine Alternative hierfür könnnte sein, dass man selber eine Resource schreibt und dies implementiert oder wie bereits erwähnt über Set-CimInstance, die entsprechende Eigenschaft des Dienst-Objektes setzt.

Falls der Artikel Gefallen gefunden hat, freue ich mich über einen Like und bin für Rückmeldungen offen.

Weiterführende Links

Golo Roden: Einführung in Docker, Folge 1: Grundkonzepte

Docker ist als Werkzeug aus der modernen Web- und Cloud-Entwicklung nicht mehr wegzudenken. Daher veröffentlichen Docker und the native web in enger Zusammenarbeit einen kostenlosen Videokurs, mit dem man den Einsatz von Docker auf einfachem Weg lernen kann.

Golo Roden: Götz & Golo: Ein Jahr später

Am 19. August 2019 hatten Götz und ich unsere Blogserie "Götz & Golo" angekündigt. Inzwischen sind zwölf Folgen erschienen. Daher ist es an der Zeit für einen kritischen Rück- und einen konstruktiven Ausblick.

Code-Inside Blog: How to run a legacy WCF .svc Service on Azure AppService

Last month we wanted to run good old WCF powered service on Azures “App Service”.

WCF… what’s that?

If you are not familiar with WCF: Good! For the interested ones: WCF is or was a framework to build mostly SOAP based services in the .NET Framework 3.0 timeframe. Some parts where “good”, but most developers would call it a complex monster.

Even in the glory days of WCF I tried to avoid it at all cost, but unfortunately I need to maintain a WCF based service.

For the curious: The project template and the tech is still there. Search for “WCF”.

VS WCF Template

The template will produce something like that:

The actual “service endpoint” is the Service1.svc file.

WCF structure

Running on Azure: The problem

Let’s assume we have a application with a .svc endpoint. In theory we can deploy this application to a standard Windows Server/IIS without major problems.

Now we try to deploy this very same application to Azure AppService and this is the result after we invoke the service from the browser:

"The resource you are looking for has been removed, had its name changed, or is temporarily unavailable." (HTTP Response was 404)

Strange… very strange. In theory a blank HTTP 400 should appear, but not a HTTP 404. The service itself was not “triggered”, because we had some logging in place, but the request didn’t get to the actual service.

After hours of debugging, testing and googling around I created a new “blank” WCF service from the Visual Studio template got the same error.

The good news: It’s was not just my code something is blocking the request.

After some hours I found a helpful switch in the Azure Portal and activated the “Failed Request tracing” feature (yeah… I could found it sooner) and I discovered this:

Failed Request tracing image

Running on Azure: The solution

My initial thoughts were correct: The request was blocked. It was treated as “static content” and the actual WCF module was not mapped to the .svc extension.

To “re-map” the .svc extension to the correct handler I needed to add this to the web.config:

...
<system.webServer>
    ...
	<handlers>
		<remove name="svc-integrated" />
		<add name="svc-integrated" path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler" resourceType="File" preCondition="integratedMode" />
	</handlers>
</system.webServer>
...

With this configuration everything worked as expected on Azure AppService.

Be aware:

I’m really not 100% sure why this is needed in the first place. I’m also not 100% sure if the name svc-integrated is correct or important.

This blogpost is a result of these tweets.

That was a tough ride… Hope this helps!

Daniel Schädler: WindowsFeatures Export in ein Desired State Configuration Script

Ziel

Mein Ziel, das Exportieren der WindowsFeatures eines Servers und diese dann in ein Desired State Configuration .ps1 zu speichern.

Voraussetzungen

  • Windows Server 2019 Core ist installiert.
  • Es sind nur die Standard-WindowsFeatures aktiviert (Standardmässig sind hier 18 installiert. Dies kann variieren).

Durchführung

Die Nachfolgenen Schritte zeigen auf, wie man dies für einen Remote Server machen sollte:

1.) Die Anmeldeinformationen mit Get-Credential in der Powershell Sitzung speichern

2.) Um effizient mit dem /den entfernten Servern arbeiten zu können, ist es empfehlenswert dies mit einer neuen Powershell Sitzung auf den Server zu machen mit New-PSSession

3.) Nun kann mit Invoke-Command auf den gewünschten Server zugegriffen werden.

4.) In dieser Sitzung, wird mit dem Cmdlet Get-WindowsFeature die gewünschten WindowsFeatures herausgefiltert.

5.) Anschliessend wird mit einer String-Variable ein Powershell-Script erstellt.

6.) Zum Schluss kann das erstellte Script ausgeführt werden. Wenn alles erfolgreich läuft, dann sieht man eine erstellte Mof-Datei, dass für Start-DscConfiguration verwendet werden kann.

PS C:\Users\U80794990> Invoke-Command -Session $session -ScriptBlock {                                                                                                                                 $features = Get-WindowsFeature | where -Property Name -like "*Web*"                                                                                                                                $script = "Configuration AspNetCoreOnIIS { `n"                                                                                                                                                       $script += "`t Import-DscResource -ModuleName 'PSDesiredStateConfiguration' `n"
 $script += "`t `t Node 'loalhost' { `n"
 foreach($feature in $features){
 $name = $feature.Name
 $script += "`t `t `t WindowsFeature $name {`n"
 $script += "`t `t `t `t Name = '$name' `n"
 $script += "`t `t `t `t Ensure = 'Present' `n"
 $script += "`t `t `t} `n"
 }
 $script += "`t `t } `n"
 $script += "`t } `n"
 $script += "AspNetCoreOnIIS -OutPutPath:`"C:\ConfigurationPath`""
 $script | Out-File C:\ServerConfiguration.ps1 -Force -Encoding utf8
 }                                                                                                                                                                                                 

Führt man nun das Script aus, mittels Invoke-Command auf dem Server aus, das generiert worden ist, so erhält man im Erfolgsfall nachfolgende Meldung.

Generierte MOF-Datei

Das generierte Script, für die WindowsFeatures sieht dann so aus: (Nur ein kleiner Ausschnitt)

Configuration AspNetCoreOnIIS { 
	 Import-DscResource -ModuleName 'DesiredStateConfiguration' 
	 	 Node 'loalhost' { 
	 	 	 WindowsFeature ADCS-Enroll-Web-Pol {
	 	 	 	 Name = 'ADCS-Enroll-Web-Pol' 
	 	 	 	 Ensure = 'Present' 
	 	 	} 
	 	 	 WindowsFeature ADCS-Enroll-Web-Svc {
	 	 	 	 Name = 'ADCS-Enroll-Web-Svc' 
	 	 	 	 Ensure = 'Present' 
	 	 	} 
.......

Nun ist es ein leichtes, einen Server für einen bestimmten Einsatzzweck vorzubereiten, indem bei den nicht gewünschten Funktionen, einfahch der Parameter "Ensure" auf Absent gesetzt werden muss.

Fazit

Ich finde die Desired State Configuration leichtgewichtig und ich denke, dass man mit dieser Art der Konfiguration vieles erledigen kann und es nicht immer ein Ainsible oder Puppet oder sonst ein Werkzeug geben muss. So kann auf eine einfach Art und Weise eine Boilerplate Konfiguration erstellt und dann für die unterschiedlichen Zwecke in einer anderen Konfiguration gespeichert werden.

Weiterführende Links

Golo Roden: Einführung in React, Folge 7: React-Forms

Die vergangenen beiden Folgen haben gezeigt, wie das Verarbeiten von Eingaben und das Verwalten von Zustand in React funktionieren. Wie lassen sich mit diesem Wissen Formulare erstellen?

Holger Schwichtenberg: GPX-Dateien verbinden mit der PowerShell

Dieses PowerShell-Skript verbindet eine beliebige Anzahl von GPX-Dateien zu einer Datei anhand von Datum und Uhrzeit.

Daniel Schädler: OpenSSH Agent /Server auf mehreren Windows Server Core 2019 installieren

Ziel

In diesem Beitrag möchte ich erläutern, wie es möglich ist den OpenSSH Server und den Agenten auf mehreren Windows Servern zu installieren und dies ohne Internetverbindung

Voraussetzungen

Um das Ziel zu erfüllen, ist es notwendig, dass die Features On Demand für Windows 2019 Server und Windows 10 (denn nur hier sind die beiden Komponenten vorhanden) zur Verfügung stehen. Diesen Artikel habe ich mir zu Hilfe genommen um das zu bewerkstelligen. Weiter gilt zu beachten dass:

  • Alle Server über WinRM /WSMan erreichbar sind
  • Der Administrator berechtigt ist, auf den Server zuzugreifen
  • Windows Server 2019 Core
  • Die Features On Demand liegen auf dem lokalen Rechner bereit um diese zu kopieren (bei mir sind diese gezipped).

Durchführung

Um sich mit den Servern zu verbinden bin ich wie folgt vorgegangen:

  1. Erstellen eines Arrays in der Powershell
$servers = "ISRV-01","ISRV-02","ISRV-03","ISRV-04"
  1. Zwischenspeichern der Anmeldeinformationen für den Zugriff auf die Server. Dies kann wie folgt bewerkstelligt werden:
$cred = Get-Credential
  1. Anschliessend wird durch das Array wie folgt iterriert:
$servers | Foreach-Object -Process {
	
}
  1. In diesem Iterationsblock wird zuerst das Features On Demand ZIP kopiert, dann eine Session auf den Zielserver erstellt und annschliessend die gewünschten Funktionen installiert.
Kopiervorgang Fortschrittsanzeige
Kopiervorgang Fortschrittsanzeige
$session = New-PSSession -ComuputerName $_ -Credential $cred
	Copy-Item C:\Temp\_FeaturesOnDemand.zip -Destination C:\ -ToSession $session
	Invoke-Command -Session $session -ScriptBlock{
		Expand-Archive C:\_FeaturesOnDemand.zip -DestinationPath C:\ -Force
		Add-WindowsCapability -Online -Name OpenSSH.Agent~~~~0.0.1.0 -Source C:\_FeaturesOnDemand
		Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Source C:\_FeaturesOnDemand
		Remove-Item C:\_FeaturesOnDemand.zip -Force
		Remove-Item C:\_FeaturesOnDemand -Recurse -Froce
}
Remove-PSSession -Id $session.Id

Den Status der Installation sieht man dann wie folgt:

Installationsfortschritt und Status
Installationsfortschritt und Status

Fazit

Mit einfachen Mitteln lassen sich ganze Serverfarmen administrieren. Natürlich könnte man nun auch die Active Directory nach gewünschten Servern Abfragen und diese dann administrieren. Zum Abschluss noch das Ganze Script.

$servers | Foreach-Object -Process {
$session = New-PSSession -ComuputerName $_ -Credential $cred
Copy-Item C:\Temp\_FeaturesOnDemand.zip -Destination C:\ -ToSession $session
	Invoke-Command -Session $session -ScriptBlock{
		Expand-Archive C:\_FeaturesOnDemand.zip -DestinationPath C:\ -Force
		Add-WindowsCapability -Online -Name OpenSSH.Agent~~~~0.0.1.0 -Source C:\_FeaturesOnDemand
		Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Source C:\_FeaturesOnDemand
		Remove-Item C:\_FeaturesOnDemand.zip -Force
		Remove-Item C:\_FeaturesOnDemand -Recurse -Froce
	}
Remove-PSSession -Id $session.Id
}

Jürgen Gutsch: ASP.NET Core Health Checks

Since a while I planned to write about the ASP.NET Health Checks which are actually pretty cool. The development of the ASP.NET Core Health Checks started in fall 2016. At that time it was a architectural draft. In November 2016 during the Global MVP Summit in Redmond we got ask to hack some health checks based on the architectural draft. It was Damien Bowden and me who met Glen Condron and Andrew Nurse during the Hackathon on the last summit day to get into the ASP.NET Health Checks and to write the very first checks and to try the framework.

Actually, I prepared a talk about the ASP.NET Health Checks. And I would be happy to do the presentation at your user group or your conference.

What are the health checks for?

Imagine that you are creating an ASP.NET application that is pretty much dependent on some sub systems, like a database, a file system, an API, or something like that. This is a pretty common scenario. Almost every application is dependent on a database. If the connection to the database got lost for different reasons, the application will definitely break. This is how applications are developed since years. The database is the simplest scenario to imagine what the ASP.NET health checks are good for, but not the real reason why they are developed. So let's continue with the database scenario.

  • What if you where able the check whether the database is available or not before you actually connect to it.
  • What if you where able to tell your application to show a user friendly message about the database that is not available.
  • What if you could simply switch to a fallback database in case the actual one is not available?
  • What if you could tell a load balancer to switch to a different fallback environment, in case your application is unhealthy because of the missing database?

You can exactly do this with the ASP.NET Health Checks:

Check the health and availability of your sub-systems, provide an endpoint that tells other systems about the health of the current application, and consume health check endpoints of other systems.

Health checks are mainly made for microservice environments. where loosely coupled applications need to know the health state of the systems they are depending on. But they are also useful in more monolithic applications that are also dependent on some kind of subsystems and infrastructure.

How to enable health checks?

I'd like to show the health check configuration in a new, plain and simple ASP.NET MVC project that I will create using the .NET CLI in my favorite console:

dotnet new mvc -n HealthCheck.MainApp -o HealthCheck.MainApp

The health checks are already in the framework and you don't need to add an separate NuGet package to use it. It is in the Microsoft.Extensions.Diagnostics.HealthChecks package that should be already available after the installation of the latest version of .NET Core.

To enable the health checks you need to add the relating services to the DI container:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks();
    services.AddControllersWithViews();
}

This is also the place where we add the checks later on. But this should be good for now.

To also provide an endpoint to tell other applications about the state of the current system you need to map a route to the health checks inside the Configure method of the Startup class:

app.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/health");
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

This will give you a URL where you can check the health state of your application. Let's quickly run the application and call this endpoint with a browser:

Celling the endpoint:

Our application is absolutely healthy. For sure, because there is no health check yet, that checks for something.

Writing health checks

Like in many other APIs (e. g. the Middlewares) there are many ways to add health checks . The simplest way and the best way to understand how it is working is to use lambda methods:

services.AddHealthChecks()
    .AddCheck("Foo", () =>
        HealthCheckResult.Healthy("Foo is OK!"), tags: new[] { "foo_tag" })
    .AddCheck("Bar", () =>
        HealthCheckResult.Degraded("Bar is somewhat OK!"), tags: new[] { "bar_tag" })
    .AddCheck("FooBar", () =>
        HealthCheckResult.Unhealthy("FooBar is not OK!"), tags: new[] { "foobar_tag" });

Those lines add three different health checks. They are named and the actual check is a Lambda expression that returns a specific HealthCheckResult. The result can be Healthy, Degraded or Unhealthy.

  • Healthy: All is fine obviously.
  • Degraded: The system is not really healthy, but it's not critical. Maybe a performance problem or something like that.
  • Unhealthy: Something critical isn't working.

Usually a health check result has at least one tag to group them by topic or whatever. The message should be meaningful to easily identify the actual problem.

Those lines are not really useful, but they show how the health check are working. If we run the app again and call the endpoint, we would see a Unhealthy state, because it always shows the lowest state, which is Unhealthy. Feel free to play around with the different HealthCheckResult

Now let's demonstrate an more useful health check. This one pings a needed resource in the internet and checks the availability:

services.AddHealthChecks()
    .AddCheck("ping", () =>
    {
        try
        {
            using (var ping = new Ping())
            {
                var reply = ping.Send("asp.net-hacker.rocks");
                if (reply.Status != IPStatus.Success)
                {
                    return HealthCheckResult.Unhealthy("Ping is unhealthy");
                }

                if (reply.RoundtripTime > 100)
                {
                    return HealthCheckResult.Degraded("Ping is degraded");
                }

                return HealthCheckResult.Healthy("Ping is healthy");
            }
        }
        catch
        {
            return HealthCheckResult.Unhealthy("Ping is unhealthy");
        }
    });

This actually won't work, because my blog runs on Azure and Microsoft doesn't allow to ping the app services. Anyway, this demo shows you how to handle the specific results and how to return the right HealthCheckResults depending on the state of the the actual check.

But it doesn't really make sense to write those tests as lambda expressions and to mess with the Startup class. Good there is a way to also add class based health checks.

Also just a simple and useless one, but it demonstrates the basic concepts:

public class ExampleHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var healthCheckResultHealthy = true;

        if (healthCheckResultHealthy)
        {
            return Task.FromResult(
                HealthCheckResult.Healthy("A healthy result."));
        }

        return Task.FromResult(
            HealthCheckResult.Unhealthy("An unhealthy result."));
    }
}

This class implements CheckHealthAsync method from the IHealthCheck interface. The HealthCheckContext contains the already registered health checks in the property Registration. This might be useful to check the state of other specific health checks.

To add this class as a health check in the application we need to use the generic AddCheck method:

services.AddHealthChecks()
    .AddCheck<ExampleHealthCheck>("class based", null, new[] { "class" });

We also need to specify a name and at least one tag. With the second argument I'm able to set a default failing state. But null is fine, in case I handle all exceptions inside the health check, I guess.

Expose the health state

As mentioned, I'm able to provide an endpoint to expose the health state of my application to systems that depends on the current app. But by default it responses just with a simple string that only shows the simple state. It would be nice to see some more details to tell the consumer what actually is happening.

Fortunately this is also possible by passing HealthCheckOptions into the MapHealthChecks method:

app.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/health", new HealthCheckOptions()
    {
        Predicate = _ => true,
        ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
    });
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

With the Predicate you are able to filter specific health checks to execute and to get the state of those. In this case I want to execute them all. The ResponseWriter is needed to write the health information of the specific checks to the response. In that case I used a ResponseWriter from a community project that provides some cool UI features and a ton of ready-to-use health checks.

dotnet add package AspNetCore.HealthChecks.UI

The UIResponseWriter of that project writes a JSON output to the HTTP response that includes many details about the used health checks:

{
  "status": "Unhealthy",
  "totalDuration": "00:00:00.7348450",
  "entries": {
    "Foo": {
      "data": {},
      "description": "Foo is OK!",
      "duration": "00:00:00.0010118",
      "status": "Healthy"
    },
    "Bar": {
      "data": {},
      "description": "Bar is somewhat OK!",
      "duration": "00:00:00.0009935",
      "status": "Degraded"
    },
    "FooBar": {
      "data": {},
      "description": "FooBar is not OK!",
      "duration": "00:00:00.0010034",
      "status": "Unhealthy"
    },
    "ping": {
      "data": {},
      "description": "Ping is degraded",
      "duration": "00:00:00.7165044",
      "status": "Degraded"
    },
    "class based": {
      "data": {},
      "description": "A healthy result.",
      "duration": "00:00:00.0008822",
      "status": "Healthy"
    }
  }
}

In case the overall state is Unhealthy the endpoint sends the result with a 503 HTTP response status, otherwise it is a 200. This is really useful if you just want to handle the HTTP response status.

The community project provides a lot more features. Also a nice UI to visualize the health state to humans. I'm going to show you this in a later section.

Handle the states inside the application

In the most cases you don't want to just expose the state to depending consumer of your app. It might also be the case that you need to handle the different states in your application, by showing a message in case the application is not working properly, disabling parts of the application that are not working, switching to a fallback source, or whatever is needed to run the application in an degraded state.

To do things like this, you can use the HealthCheckService that is already registered to the IoC Container with the AddHealthChecks() method. You can inject the HealthCheckService using the IHealthCheckService interface wherever you want.

Let's see how this is working!

In the HomeController I created a constructor that injects the IHealthCheckService the same way as other services need to be injected. I also created a new Action called Health that uses the HealthCheckService and calls the method CheckHealthAsync() to execute the checks and to retrieve a HealthReport. The HealthReport is than passed to the view:

public class HomeController : Controller
{
    private readonly IHealthCheckService _healthCheckService;

    public HomeController(
        IHealthCheckService healthCheckService)
    {
        _healthCheckService = healthCheckService;
    }

    public async Task<IActionResult> Health()
    {
        var healthReport = await _healthCheckService.CheckHealthAsync();
        
        return View(healthReport);
    }

Optionally you are able to pass a predicate to the method CheckHealthAsync(). With the Predicate you are able to filter specific health checks to execute and to get the state of those. In this case I want to execute them all.

I also created a view called Health.cshtml. This view retrieves the HealthReport and displays the results:

@using Microsoft.Extensions.Diagnostics.HealthChecks;
@model HealthReport

@{
    ViewData["Title"] = "Health";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's health.</p>

<p>
    <span>@Model.Status</span> - <span>Duration: @Model.TotalDuration.TotalMilliseconds</span>
</p>
<ul>
    @foreach (var entry in Model.Entries)
    {
    <li>
        @entry.Value.Status - @entry.Value.Description<br>
        Tags: @String.Join(", ", entry.Value.Tags)<br>
        Duration: @entry.Value.Duration.TotalMilliseconds
    </li>
    }
</ul>

To try it out, I just need to run the application using dotnet run in the console and calling https://localhost:5001/home/health in the browser:

You could also try to analyze the HealthReport in the Controller, in your services to do something specific in case the the application isn't healthy anymore.

A pretty health state UI

The already mentioned GitHub project AspNetCore.Diagnostics.HealthChecks also provides a pretty UI to display the results in a nice and human readable way.

This just needs a little more configuration in the Startup.cs

Inside the method ConfigureServices() I needed to add the health checks UI services

services.AddHealthChecksUI();

And inside the method Configure() I need to map the health checks UI Middleware right after the call of MapHealthChecks:

endpoints.MapHealthChecksUI();

This adds a new route to our application to call the UI: /healthchecks-ui

We also need to register our health API to the UI. This will be done using small setting to the appsetings.json:

{
  ... ,
  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "HTTP-Api",
        "Uri": "https://localhost:5001/health"
      }
    ],
    "EvaluationTimeOnSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  }
}

This way you are able to register as many health endpoints to the UI as you like. Think about a separate application that only shows the health states of all your microservices. This would be the way to go.

Let's call the UI using this route /healthchecks-ui

(Wow... Actually, the ping seemed to work, when I did this screenshot. )

This is awesome. This is a really great user interface to display the health of all your services.

About the Webhooks and customization of the UI, you should read the great docs in the repository.

Conclusion

The health checks are definitely a thing you should look into. No matter what kind of web application you are writing, it can help you to create more stable and more responsive applications. Applications that know about their health can handle degraded of unhealthy states in a way that won't break the whole application. This is very useful, at least from my perspective ;-)

To play around with the demo application used for this post visit the repository on GitHub: https://github.com/JuergenGutsch/healthchecks-demo

Marco Scheel: Enable Unified Labeling for Microsoft 365 Groups (and Teams) in your tenant via PowerShell script

Microsoft announced end of June 2020 the “General Availability” of the Microsoft Information Protection integration for Group labeling. Unified labeling is now available für all Microsoft 365 Groups (Teams, SharePoint, …).

Microsoft Information Protection is a built-in, intelligent, unified, and extensible solution to protect sensitive data across your enterprise – in Microsoft 365 cloud services, on-premises, third-party SaaS applications, and more. Microsoft Information Protection provides a unified set of capabilities to know your data, protect your data, and prevent data loss across Microsoft 365 apps (e.g. Word, PowerPoint, Excel, Outlook) and services (e.g. Teams, SharePoint, and Exchange).

Source: https://techcommunity.microsoft.com/t5/microsoft-security-and/general-availability-microsoft-information-protection/ba-p/1497769

The feature is currently an opt-in solution. The previous Azure AD based group classification is still available and supported. If you want to switch to the new solution to apply sensitivity labels to your groups you need to run some lines of PowerShell. This is the Microsoft documentation:

https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/groups-assign-sensitivity-labels#enable-sensitivity-label-support-in-powershell

The feature is configured with the same commands as the AAD based classification. You have to set the value for “EnableMIPLabels“ to true.The documentation is expecting that you already have Azure AD directory settings for the template “Group.Unified“. If this is not the case you can also follow the instructions on the Azure AD directory settings for Groups:

https://docs.microsoft.com/en-us/azure/active-directory/users-groups-roles/groups-settings-cmdlets#create-settings-at-the-directory-level

To make it easier for my customers and for you, I’ve created a PowerShell that will help and work in any configuration. Check out the latest version of my script in this GitHub repository:

https://github.com/marcoscheel/snippets/blob/master/m365-enable-ul-groups/enable-ulclassification.ps1

$tenantdetail = $null;
$tenantdetail = Get-AzureADTenantDetail -ErrorAction SilentlyContinue; 
if ($null -eq $tenantdetail)
{
    #connect as gloabl admin
    Connect-AzureAD
    $tenantdetail = Get-AzureADTenantDetail -ErrorAction SilentlyContinue; 
}
if ($null -eq $tenantdetail)
{
    Write-Host "Error connecting to tenant" -ForegroundColor Red;
    Exit
}

$settingIsNew = $false;
$setting = Get-AzureADDirectorySetting | Where-Object { $_.DisplayName -eq "Group.Unified"};
if ($null -eq $setting){
    Write-Host "Not directory settings for Group.Unified found. Create new!" -ForegroundColor Green;
    $settingIsNew = $true;
    $aaddirtempid = (Get-AzureADDirectorySettingTemplate | Where-Object { $_.DisplayName -eq "Group.Unified" }Id;
    $template = Get-AzureADDirectorySettingTemplate -Id $aaddirtempid;
    $setting = $template.CreateDirectorySetting();
}
else{
    Write-Host "Directory settings for Group.Unified found. Current value for EnableMIPLabels:" oregroundColor Green;
    Write-Host $setting["EnableMIPLabels"];
}

$setting["EnableMIPLabels"] = "true";
if (-not $settingIsNew){
    #Reset AAD based classsification?
    #$setting["ClassificationList"] = "";
    #$setting["DefaultClassification"] = "";
    #$setting["ClassificationDescriptions"] = "";
}

if ($settingIsNew){

    New-AzureADDirectorySetting -DirectorySetting $setting;
    Write-Host "New directory settings for Group.Unified applied." -ForegroundColor Green;
    $setting = Get-AzureADDirectorySetting | Where-Object { $_.DisplayName -eq "Group.Unified"};
}
else{
    Set-AzureADDirectorySetting -Id $setting.Id -DirectorySetting $setting;
    Write-Host "Updated directory settings for Group.Unified." -ForegroundColor Green;
    $setting = Get-AzureADDirectorySetting | Where-Object { $_.DisplayName -eq "Group.Unified"};
}
$setting.Values;

Holger Schwichtenberg: Inhalt eines ZIP-Archives mit PowerShell auflisten, ohne es zu entpacken

Für das Auflisten des Inhalts von ZIP-Archiven existiert kein Commandlet, aber man kann die .NET-Klasse System.IO.Compression.ZipFile in der PowerShell nutzen.

Christian Dennig [MS]: Azure DevOps Terraform Provider

Not too long ago, the first version of the Azure DevOps Terraform Provider was released. In this article I will show you with several examples which features are currently supported in terms of build pipelines and how to use the provider – also in conjunction with Azure. The provider is the last “building block” for many people working in the “Infrastructure As Code” space to create environments (including Git Repos, service connections, build + release pipelines etc.) completely automatically.

The provider was released in June 2020 in version 0.0.1, but to be honest: the feature set is quite rich already at this early stage.

The features I would like to discuss with the help of examples are as follows:

  • Create a DevOps project including a hosted Git repo.
  • Creation of a build pipeline
  • Usage of variables and variable groups
  • Creating an Azure service connection and using variables/secrets from an Azure KeyVault

Example 1: Basic Usage

The Azure DevOps provider can be integrated in a script like any other Terraform provider. All that’s required is the URL to the DevOps organisation and a Personal Access Token (PAT) with which the provider can authenticate itself against Azure DevOps.

The PAT can be easily created via the UI of Azure DevOps by creating a new token via User Settings --> Personal Access Token --> New Token. For the sake of simplicity, in this example I give “Full Access” to it…of course this should be adapted for your own purposes.

Create a personal access token

The documentation of the Terraform Provider contains information about the permissions needed for the respective resource.

Defining relevant scopes

Once the access token has been created, the Azure DevOps provider can be referenced in the terraform script as follows:

provider "azuredevops" {
  version               = ">= 0.0.1"
  org_service_url       = var.orgurl
  personal_access_token = var.pat
}

The two variables orgurl and pat should be exposed as environment variables:

$ export TF_VAR_orgurl = "https://dev.azure.com/<ORG_NAME>"
$ export TF_VAR_pat = "<PAT_FROM_AZDEVOPS>"

So, this is basically all that is needed to work with Terraform and Azure DevOps. Let’s start by creating a new project and a git repository. Two resources are needed for this, azuredevops_project and azuredevops_git_repository:

resource "azuredevops_project" "project" {
  project_name       = "Terraform DevOps Project"
  description        = "Sample project to demonstrate AzDevOps <-> Terraform integragtion"
  visibility         = "private"
  version_control    = "Git"
  work_item_template = "Agile"
}

resource "azuredevops_git_repository" "repo" {
  project_id = azuredevops_project.project.id
  name       = "Sample Empty Git Repository"

  initialization {
    init_type = "Clean"
  }
}

Additionally, we also need an initial pipeline that will be triggered on a git push to master.
In a pipeline, you usually work with variables that come from different sources. These can be pipeline variables, values from a variable group or from external sources such as an Azure KeyVault. The first, simple build definition uses pipeline variables (mypipelinevar):

resource "azuredevops_build_definition" "build" {
  project_id = azuredevops_project.project.id
  name       = "Sample Build Pipeline"

  ci_trigger {
    use_yaml = true
  }

  repository {
    repo_type   = "TfsGit"
    repo_id     = azuredevops_git_repository.repo.id
    branch_name = azuredevops_git_repository.repo.default_branch
    yml_path    = "azure-pipeline.yaml"
  }

  variable {
    name      = "mypipelinevar"
    value     = "Hello From Az DevOps Pipeline!"
    is_secret = false
  }
}

The corresponding pipeline definition looks as follows:

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Pipeline is running!
    echo And here is the value of our pipeline variable
    echo $(mypipelinevar)
  displayName: 'Run a multi-line script'

The pipeline just executes some scripts – for demo purposes – and outputs the variable stored in the definition to the console.

Running the Terraform script, it creates an Azure DevOps project, a git repository and a build definition.

Azure DevOps Project
Git Repository
Pipeline

As soon as the file azure_pipeline.yaml discussed above is pushed into the repo, the corresponding pipeline is triggered and the results can be found in the respective build step:

Running pipeline
Output of build pipeline

Example 2: Using variable groups

Normally, variables are not directly stored in a pipeline definition, but rather put into Azure DevOps variable groups. This allows you to store individual variables centrally in Azure DevOps and then reference and use them in different pipelines.

Fortunately, variable groups can also be created using Terraform. For this purpose, the resource azuredevops_variable_group is used. In our script this looks like this:

resource "azuredevops_variable_group" "vars" {
  project_id   = azuredevops_project.project.id
  name         = "my-variable-group"
  allow_access = true

  variable {
    name  = "var1"
    value = "value1"
  }

  variable {
    name  = "var2"
    value = "value2"
  }
}

resource "azuredevops_build_definition" "buildwithgroup" {
  project_id = azuredevops_project.project.id
  name       = "Sample Build Pipeline with VarGroup"

  ci_trigger {
    use_yaml = true
  }

  variable_groups = [
    azuredevops_variable_group.vars.id
  ]

  repository {
    repo_type   = "TfsGit"
    repo_id     = azuredevops_git_repository.repo.id
    branch_name = azuredevops_git_repository.repo.default_branch
    yml_path    = "azure-pipeline-with-vargroup.yaml"
  }
}

The first part of the terraform script creates the variable group in Azure DevOps (name: my-variable-group) including two variables (var1 and var2), the second part – a build definition – uses the variable group, so that the variables can be accessed in the corresponding pipeline file (azure-pipeline-with-vargroup.yaml).

It has the following content:

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
- group: my-variable-group

steps:
- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo Var1: $(var1)
    echo Var2: $(var2)
  displayName: 'Run a multi-line script'

If you run the Terraform script, the corresponding Azure DevOps resources will be created: a variable group and a pipeline.

Variable Group

If you pusht the build YAML file to the repo, the pipeline will be executed and you should see the values of the two variables as output on the build console.

Output of the variables from the variable group

Example 3: Using an Azure KeyVault and Azure DevOps Service Connections

For security reasons, critical values are neither stored directly in a pipeline definition nor in Azure DevOps variable groups. You would normally use an external vault like Azure KeyVault. Fortunately, with Azure DevOps you have the possibility to access an existing Azure KeyVault directly and access secrets which are then made available as variables within your build pipeline.

Of course, Azure DevOps must be authenticated/authorized against Azure for this. Azure DevOps uses the concept of service connections for this purpose. Service connections are used to access e.g. Bitbucket, GitHub, Jira, Jenkis…or – as in our case – Azure. You define a user – for Azure this is a service principal – which is used by DevOps pipelines to perform various tasks – in our example fetching a secret from a KeyVault.

To demonstrate this scenario, various things must first be set up on Azure:

  • Creating an application / service principal in the Azure Active Directory, which is used by Azure DevOps for authentication
  • Creation of an Azure KeyVault (including a resource group)
  • Authorizing the service principal to the Azure KeyVault to be able to read secrets (no write access!)
  • Creating a secret that will be used in a variable group / pipeline

With the Azure Provider, Terraform offers the possibility to manage Azure services. We will be using it to create the resources mentioned above.

AAD Application + Service Principal

First of all, we need a service principal that can be used by Azure DevOps to authenticate against Azure. The corresponding Terraform script looks like this:

data "azurerm_client_config" "current" {
}

provider "azurerm" {
  version = "~> 2.6.0"
  features {
    key_vault {
      purge_soft_delete_on_destroy = true
    }
  }
}

## Service Principal for DevOps

resource "azuread_application" "azdevopssp" {
  name = "azdevopsterraform"
}

resource "random_string" "password" {
  length  = 24
}

resource "azuread_service_principal" "azdevopssp" {
  application_id = azuread_application.azdevopssp.application_id
}

resource "azuread_service_principal_password" "azdevopssp" {
  service_principal_id = azuread_service_principal.azdevopssp.id
  value                = random_string.password.result
  end_date             = "2024-12-31T00:00:00Z"
}

resource "azurerm_role_assignment" "contributor" {
  principal_id         = azuread_service_principal.azdevopssp.id
  scope                = "/subscriptions/${data.azurerm_client_config.current.subscription_id}"
  role_definition_name = "Contributor"
}

With the script shown above, both an AAD Application and a service principal are generated. Please note that the service principal is assigned the role Contributor – on subscription level, see the scope assignment. This should be restricted accordingly in your own projects (e.g. to the respective resource group)!

Azure KeyVault

The KeyVault is created the same way as the previous resources. It is important to note that the user working against Azure is given full access to the secrets in the KeyVault. Further down in the script, permissions for the Azure DevOps service principal are also granted within the KeyVault – but in that case only read permissions! Last but not least, a corresponding secret called kvmysupersecretsecret is created, which we can use to test the integration.

resource "azurerm_resource_group" "rg" {
  name     = "myazdevops-rg"
  location = "westeurope"
}

resource "azurerm_key_vault" "keyvault" {
  name                        = "myazdevopskv"
  location                    = "westeurope"
  resource_group_name         = azurerm_resource_group.rg.name
  enabled_for_disk_encryption = true
  tenant_id                   = data.azurerm_client_config.current.tenant_id
  soft_delete_enabled         = true
  purge_protection_enabled    = false

  sku_name = "standard"

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    secret_permissions = [
      "backup",
      "get",
      "list",
      "purge",
      "recover",
      "restore",
      "set",
      "delete",
    ]
    certificate_permissions = [
    ]
    key_permissions = [
    ]
  }

}

## Grant DevOps SP permissions

resource "azurerm_key_vault_access_policy" "azdevopssp" {
  key_vault_id = azurerm_key_vault.keyvault.id

  tenant_id = data.azurerm_client_config.current.tenant_id
  object_id = azuread_service_principal.azdevopssp.object_id

  secret_permissions = [
    "get",
    "list",
  ]
}

## Create a secret

resource "azurerm_key_vault_secret" "mysecret" {
  key_vault_id = azurerm_key_vault.keyvault.id
  name         = "kvmysupersecretsecret"
  value        = "KeyVault for the Win!"
}

If you have followed the steps described above, the result in Azure is a newly created KeyVault containing one secret:

Azure KeyVault

Service Connection

Now, we need the integration into Azure DevOps, because we finally want to access the newly created secret in a pipeline. Azure DevOps is “by nature” able to access a KeyVault and the secrets it contains. To do this, however, you have to perform some manual steps – when not using Terraform – to enable access to Azure. Fortunately, these can now be automated with Terraform. The following resources are used to create a service connection to Azure in Azure DevOps and to grant access to our project:

## Service Connection

resource "azuredevops_serviceendpoint_azurerm" "endpointazure" {
  project_id            = azuredevops_project.project.id
  service_endpoint_name = "AzureRMConnection"
  credentials {
    serviceprincipalid  = azuread_service_principal.azdevopssp.application_id
    serviceprincipalkey = random_string.password.result
  }
  azurerm_spn_tenantid      = data.azurerm_client_config.current.tenant_id
  azurerm_subscription_id   = data.azurerm_client_config.current.subscription_id
  azurerm_subscription_name = "<SUBSCRIPTION_NAME>"
}

## Grant permission to use service connection

resource "azuredevops_resource_authorization" "auth" {
  project_id  = azuredevops_project.project.id
  resource_id = azuredevops_serviceendpoint_azurerm.endpointazure.id
  authorized  = true 
}
Service Connection

Creation of an Azure DevOps variable group and pipeline definition

The last step necessary to use the KeyVault in a pipeline is to create a corresponding variable group and “link” the existing secret.

## Pipeline with access to kv secret

resource "azuredevops_variable_group" "kvintegratedvargroup" {
  project_id   = azuredevops_project.project.id
  name         = "kvintegratedvargroup"
  description  = "KeyVault integrated Variable Group"
  allow_access = true

  key_vault {
    name                = azurerm_key_vault.keyvault.name
    service_endpoint_id = azuredevops_serviceendpoint_azurerm.endpointazure.id
  }

  variable {
    name    = "kvmysupersecretsecret"
  }
}
Variable Group with KeyVault integration

Test Pipeline

All prerequisites are now in place, but we still need a pipeline with which we can test the scenario.

Script for the creation of the pipeline:

resource "azuredevops_build_definition" "buildwithkeyvault" {
  project_id = azuredevops_project.project.id
  name       = "Sample Build Pipeline with KeyVault Integration"

  ci_trigger {
    use_yaml = true
  }

  variable_groups = [
    azuredevops_variable_group.kvintegratedvargroup.id
  ]

  repository {
    repo_type   = "TfsGit"
    repo_id     = azuredevops_git_repository.repo.id
    branch_name = azuredevops_git_repository.repo.default_branch
    yml_path    = "azure-pipeline-with-keyvault.yaml"
  }
}

Pipeline definition (azure-pipeline-with-keyvault.yaml):

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
- group: kvintegratedvargroup

steps:
- script: echo Hello, world!
  displayName: 'Run a one-line script'

- script: |
    echo KeyVault secret value: $(kvmysupersecretsecret)
  displayName: 'Run a multi-line script'

If you have run the Terraform script and pushed the pipeline file into the repo, you will get the following result in the next build (the secret is not shown in the console for security reasons, of course!):

Output: KeyVault integrated variable group

Wrap-Up

Setting up new Azure DevOps projects was not always the easiest task, as sometimes manual steps were required. With the release of the first Terraform provider version for Azure DevOps, this has changed almost dramatically 🙂 You can now – as one of the last building blocks for automation in a dev project – create many things via Terraform in Azure DevOps. In the example shown here, the access to an Azure KeyVault including the creation of the corresponding service connection could be achieved. However, only one module was shown here – frankly, one for a task that “annoyed” me every now and then, as most of it had to be set up manually before having a Terraform provider. The provider can also manage branch policies, set up groups and group memberships etc. With this first release you are still “at the beginning of the journey”, but in my opinion, it is a “very good start” with which you can achieve a lot.

I am curious what will be supported next!

Sample files can be found here: https://gist.github.com/cdennig/4866a74b341a0079b5a59052fa735dbc

Golo Roden: Standardlösung oder Eigenbau?

In der Softwareentwicklung steht man häufig vor der Wahl, eine fertige Standardlösung von der Stange zu verwenden oder eine Eigenentwicklung durchzuführen. Was ist ratsam?

Code-Inside Blog: EWS, Exchange Online and OAuth with a Service Account

This week we had a fun experiment: We wanted to talk to Exchange Online via the “old school” EWS API, but in a “sane” way.

But here is the full story:

Our goal

We wanted to access contact information via a web service from the organization, just like the traditional “Global Address List” in Exchange/Outlook. We knew that EWS was on option for the OnPrem Exchange, but what about Exchange Online?

The big problem: Authentication is tricky. We wanted to use a “traditional” Service Account approach (think of username/password). Unfortunately the “basic auth” way will be blocked in the near future because of security concerns (makes sense TBH). There is an alternative approach available, but at first it seems not to work as we would like.

So… what now?

EWS is… old. Why?

The Exchange Web Services are old, but still quite powerful and still supported for Exchange Online and OnPrem Exchanges. On the other hand we could use the Microsoft Graph, but - at least currently - there is not a single “contact” API available.

To mimic the GAL we would need to query List Users and List orgContacts, which would be ok, but the “orgContacts” has a “flaw”. “Hidden” contacts (“msexchhidefromaddresslists”) are returned from this API and we thought that this might be a NoGo for our customers.

Another argument for using EWS was, that we could support OnPrem and Online with one code base.

Docs from Microsoft

The good news is, that EWS and the Auth problem is more or less good documented here.

There are two ways to authenticate against the Microsoft Graph or any Microsoft 365 API: Via “delegation” or via “application”.

Delegation:

Delegation means, that we can write a desktop app and all actions are executed in the name of the signed in user.

Application:

Application means, that the app itself can do some actions without any user involved.

EWS and the application way

At first we thought that we might need to use the “application” way.

The good news is, that this was easy and worked. The bad news is, that the application needs the EWS permission “full_access_as_app”, which means that our application can access all mailboxes from this tenant. This might be ok for certain apps, but this scared us.

Back to the delegation way:

EWS and the delegation way

The documentation from Microsoft is good, but our “Service Account” usecase was not mentioned. In the example from Microsoft a user needs to manually login.

Solution / TL;DR

After some research I found the solution to use a “username/password” OAuth flow to access a single mailbox via EWS:

  1. Follow the normal “delegate” steps from the Microsoft Docs

  2. Instead of this code, which will trigger the login UI:

...
// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" };

// Make the interactive token request
var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();
...

Use the “AcquireTokenByUsernamePassword” method:

...
var cred = new NetworkCredential("UserName", "Password");
var authResult = await pca.AcquireTokenByUsernamePassword(new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" }, cred.UserName, cred.SecurePassword).ExecuteAsync();
...

To make this work you need to enable the “Treat application as public client” under “Authentication” > “Advanced settings” in our AAD Application because this uses the “Resource owner password credential flow”.

Now you should be able to get the AccessToken and do some EWS magic.

I posted a shorter version on Stackoverflow.com

Hope this helps!

Daniel Schädler: App Deployment mit Minikube /kubectl

Ziel

In diesem Beitrag möchte ich in meiner Umgebung die Applikations Verteilungen (Deployments) lernen. Weiter möchte ich eine .NET Applikation auf den Cluster mit kubectl verteilen.

Ich folge den hier beschriebenen Scrhitte und verwende eine einfache .NET Core Applikation für das Deployment.

Voraussetzungen

Um diese Aufgabe zu erfüllen sind folgende Voraussetzungen gegeben:

  • Minikube ist installiert
  • Eine .NET Core App ist vorhanden um auf einen Container verteilt werden zu können.
  • Docker für Windows muss installiert sein
  • Unter Umständen muss das LINUX Subsystem für Windows installiert sein, wenn ihr die entsprechende Option im Docker-Installer ausgewählt habt.

Durchführung

Um das oben genannte Ziel zu erreichen muss ein Kubernetes Deployment erstellt werden. Für die Erstellung eines Deploments wird das CLI kubectl verwendet, dass in meinem Fall im C:\Program Files\kubectl installiert worden ist.

  1. Starten der Powershell als Administrator
  2. Anschliessend den minikube starten, wenn er noch nicht gestartet worden ist.

Der erfolgreiche Start sieht dann so aus:

Erfolgreicher Minikube Start in der Powershell

Werden beim Starten Fehler festgestellt, so sollte zuerst mit minikube stop versucht werden das Ganze zu stoppen und wieder zu starten.

Den Status kann man dann damit überprüfen:

minikube status

Un erhält dann die folgende Meldung:

Minikube Status – Powershell
  1. Nun wird der Befehl
kubectl version
kubectl get nodes

ausgeführt.

Das sieht dann so aus:

kubectl version und nodes in der Powershell

Es ist ersichtlich, dass der Client und der Server verfügbar sind. Weiter ist ersichtlich, dass ein verfügbarer Knoten (Node) da ist. Kubernetes verwendet für die Applikationsverteilung die verfügbaren Ressourcen auf Basis der Knoten (Nodes).

  1. Nun kommt der spannende Teil lokal in der Powershell. Hier muss die .NET Core Applikation angegeben werden, die verteilt werden soll. Hierzu habe ich eine Beispiel-Applikation analog dem hier beschriebenen Vorgehen erstellt.

Das Docker-Image ist mit dem Befehl

docker build -t app-demo -f Dockerfile .

erstellt worden.

Anschliessend kann man überprüfen ob das Image in der lokalen Registry vorhanden ist mit:

docker images
App-Demo in der lokalen Registry – Powershell
  1. Anschliessend habe ich dann mit kubectl ein Deplyoment erstellt, das auf die lokale Docker Registry geht.
kubectl create deployment app-demo --image=app-demo:latest

kubectl teil mir dann mit, dass das Deployment erfolgreich erstellt worden ist.

Deployment ist erstellt worden – Powershell

Fazit

Das Ganze, sieht einfach aus aber der eine oder andere Fallstrick ist dann doch vorhanden. Wenn zum Beispiel das .NET Core Image nicht von einer zentralen Registry runtergeladen werden kann, sondern selber gehostet werden muss, aus Sicherheitsgründen oder politischen Vorgaben. Falls der Artikel Gefallen gefunden hat, freue ich mich über einen Like und bin für Rückmeldungen offen.

Holger Schwichtenberg: Buch zu Blazor WebAssembly und Blazor Server

Das aktuelle Fachbuch des Dotnet-Doktors bietet den Einstieg in Microsofts neues Webframework.

Jürgen Gutsch: Getting the .editorconfig working with the .NET Framework and MSBuild

I demonstrated my results of the last post about the .editorconfig to the team last week. They were quite happy about the fact that the build fails on a code style error but there was one question I couldn't really answer. The Question was:

Does this also work for .NET Framework?

It should, because it is Roslyn who analyses the code. It is not any framework who does it.

To try it out I created three different class libraries that have the same class file linked into it, with the same code style errors:

    using System;

namespace ClassLibraryNetFramework
{
    public class EditorConfigTests
    {
    public int MyProperty { get; } = 1;
    public EditorConfigTests() { 
    if(this.MyProperty == 2){
        Console.WriteLine("Hallo Welt");
        }        }
    }
}

This code file has at least eleven code style errors in it:

I created a .NET Standard library, a .NET Core library, and a .NET Framework library in VS2019 this time. The solution in VS2019 now looks like this:

I also added the MyGet Roslyn NuGet Feed to the NuGet sources and referenced the code style analyzers:

This is the URL and the package name for you to copy:

  • https://dotnet.myget.org/F/roslyn/api/v3/index.json
  • Microsoft.CodeAnalysis.CSharp.CodeStyle Version: 3.8.0-1.20330.5

I also set the global.json to the latest preview of the .NET 5 SDK to be sure to use the latest tools:

{
  "sdk": {
    "version": "5.0.100-preview.6.20318.15"
  }
}

It didn't really work - My fault in the last blog post!

I saw some code style errors in VS2019 but not all the eleven errors I expected. I tried a build and the build didn't fail. Because I knew it worked the last time I tried it using the the dotnet CLI. I did the same here. I ran dotnet build and dotnet msbuild but the build didn't fail.

This is exactly what you don't need as a software developer: Doing things exactly the same twice and one time it works and on the other time it fails and you have no idea why.

I tried a lot of things and compared project files, solution files, and .editorconfig files. Actually I compared it with the Weather Stats application I used in the last post. At the end I found one line in the the PropertyGroup of the project files of the weather application that shouldn't be there but actually was the reason why it worked.

<CodeAnalysisRuleSet>..\editorconfig.ruleset</CodeAnalysisRuleSet>

While trying to get it running for the last post, I also experimented with a ruleset file. The ruleset file is a XML file that can be used to enable or disable analysis rules in VS2019. I added a ruleset file to the solution and linked it into the projects, but forgot about that.

So it seemed the failing builds of the last post wasn't because of the .editorconfig but because of this ruleset file.

It also seemed the ruleset file is needed to get it working. That shouldn't be the case and I asked the folks via the GitHub Issue about that. The answer was fast:

  • Fakt #1: The ruleset file isn't needed

  • Fakt #2: The regular .editorconfig entries don't work yet

The solution

Currently the ruleset entries where moved to the .editorconfig this means you need to add IDE specific entries to the .editorconfig to get it running, which also means you will have redundant entries until all the code style analyzers are moved to Roslyn and are mapped to the .editorconfig:

# IDE0007: Use 'var' instead of explicit type
dotnet_diagnostic.IDE0007.severity = error

# IDE0055 Fix formatting
dotnet_diagnostic.IDE0055.severity = error

# IDE005_gen: Remove unnecessary usings in generated code
dotnet_diagnostic.IDE0005_gen.severity = error

# IDE0065: Using directives must be placed outside of a namespace declaration
dotnet_diagnostic.IDE0065.severity = error

# IDE0059: Unnecessary assignment
dotnet_diagnostic.IDE0059.severity = error

# IDE0003: Name can be simplified
dotnet_diagnostic.IDE0003.severity = error  

As mentioned, these entries are already in the .editorconfig but written differently.

In the GitHub Issue they also wrote to add a specific line, in case you don't know all the IDE numbers. This line writes out warnings for all the possible code style failures. You'll see the numbers in the warning output and you can now configure how the code style failure should be handled:

# C# files
[*.cs]
dotnet_analyzer_diagnostic.category-Style.severity = warning

This solves the problem and it actually works really good.

Conclusion

Even if it solves the problem, I really hope this is a intermediate solution only, because of the redundant entries in the .editorconfig. I would prefer to not have the IDE specific entries, but I guess this needs some more time and a lot work done by Microsoft.

Holger Schwichtenberg: AsNoTrackingWithIdentityResolution() in Entity Framework Core 5.0 ab Preview 7

Microsoft hat den Namen für die forcierte Identitätsfeststellung beim Laden im "No-Tracking"-Modus geändert.

Jürgen Gutsch: .NET Interactive in Jupyter Notebooks

Since almost a year I do a lot of Python projects. Actually Python isn't that bad. Python and Flask to build web applications work almost similar to NodeJS and ExpressJS. Similarly to NodeJS, Python development is really great using Visual Studio Code.

People who are used to use Python know Jupyter Notebooks to create interactive documentations. Interactive documentation means that the code snippets are executable and that you can use Python code to draw charts or to calculate and display data.

If I got it right, Jupyter Notebook was IPython in the past. Now Jupyter Notebook is a standalone project and the IPython project focuses on Python Interactive and Python kernels for Jupyter Notebook.

The so called kernels extend Jupyter Notebook to execute a specific language. The Python kernel is default. You are able to install a lot more kernels. There are kernels for NodeJS and more.

Microsoft is working on .NET Interactive and kernels for Jupyter Notebook. You are now able to write interactive documentations in Jupyter Notebook using C#, F# and PowerShell, as well.

In this blog post I'll try to show you how to install and to use it.

Install Jupyter Notebook

You need to have Python3 installed on your machine. The best way to install Python on Windows is to use Chocolatey:

choco install python

Actually I use Chocolatey since many years as a Windows package manager and never had any problems.

Alternatively you could download and install Pythion 3 directly or by using the Anaconda installer.

If Python is installed you can install Jupyter Notebook using the Python package manager PIP:

pip install notebook

You now can use Jupyter by just type jupyter notebook in the console. This would start the Notebook with the default Python3 kernel. The following command shows the installed kernels:

jupyter kernelspec list

We'll see the python3 kernel in the console output:

Install .NET Interactive

The goal is to have the .NET Interactive kernels running in Jupyter. To get this done you first need to install the latest build of .NET Interactive from MyGet:

dotnet tool install -g --add-source "https://dotnet.myget.org/F/dotnet-try/api/v3/index.json" Microsoft.dotnet-interactive

Since NuGet is not the place to publish continuous integration build artifacts, Microsoft uses MyGet as well to publish previews, nightly builds, and continuous integration build artifacts.

Or install the latest stable version from NuGet:

dotnet tool install -g Microsoft.dotnet-interactive

If this is installed you can use dotnet interactive to install the kernels to Jupyter Notebooks

dotnet interactive jupyter install

Let's see, whether the kernels are installed or not:

jupyter kernelspec list

listkernels02

That's it. We now have four different kernels installed.

Run Jupyter Notebook

Let's run Jupyter by calling the next command. Be sure to navigate into a folder where your notebooks are or where you want to save your notebooks:

cd \git\hub\dotnet-notebook
jupyter notebook

startnotebook

It now starts a webserver that serves the notebooks from the current location and opens a Browser. The current folder will be the working folder for the currently running Jupyter instance. I don't have any files in that folder yet.

Here we have the Python3 and the three new .NET notebook types available:

notebook01

I now want to start playing around with a C# based notebook. So I create a new .NET (C#) notebook:

Try .NET Interactive

Let's add some content and a code snippet. At first I added a Markdown cell.

The so called "cells" are content elements the support specific content types. A Markdown cell is one type as well as a Code cell. The later executes a code snippet and shows the output underneath:

notebook02

That is a easy one. Now let's play with variables usage. I placed two more code cells and some small markdown cells below:

notebook03

And re-run the entire notebook:

notebook04

As well as in Python notebooks the variables are used and valid in the entire notebook and not only in the single code cell.

What else?

Inside a .NET Interactive notebook you can do the same stuff as in a regular code file. You are able to connect to a database, to Azure or just open a file locally on your machine. You can import namespaces as well as reference NuGet packages:

#r "nuget:NodaTime,2.4.8"
#r "nuget:Octokit,0.47.0"

using Octokit;
using NodaTime;
using NodaTime.Extensions;
using XPlot.Plotly;

VS Code

VS Code is also supporting Jupyter Notebooks usingMicrosoft's Python Add-In:

vscode01

Actually, it needs a couple of seconds until the Jupyter server is started. If it is up and running, it works like charm in VS Code. I really prefer VS Code over the browser interface to write notebooks.

GitHub

If you use a notepad to open a notebook file, you will see that it is a JSON file that also contains the outputs of the code cells:

vscode01

Because of that, I was really surprised that GitHub supports Jupyter Notebooks as well and displays it in a human readable format including the outputs. I expected to see the source code of the notebook, instead of the output:

vscode01

The rendering is limited but good enough to read the document. This means, it could make sense to write a notebook instead of a simple markdown file on GitHub.

Conclusion

I really like the concept of the interactive documentations. This is pretty common in the data science, analytics, and statistics universe. Python developers, as well as MatLab developers know that concept.

Personally I see a great benefit in other areas, too. Like learning, library and API documentation, as well as in all documentations that focus on code.

I also see a benefit on documentations about production lines, where several machines working together in a chain. Since you are able to use and execute .NET code, you could connect to machine sensors to read the state of the machines to display it in the documentation. The maintaining people are now able to see the state directly in the documentation of the production line.

Marco Scheel: Beware of the Teams Admin center to create new teams (and assign owners)

The Microsoft Teams Admin Center can be used to create a new Team. The initial dialog allows you to set multiple owners for the Team. This feature was added over time and is a welcome addition to make the life of an administrator easier. But the implementation has a big shortcoming: The specified owners in this dialog will not become a member of the underlying Microsoft 365 Group in Azure Active Directory. As a result all Microsoft 365 group services checking for members will not behave as expected. For example: These owners will not be able to access Planner. Other services like Teams and SharePoint work by accident. image

Let’s start with some basic information. Office Microsoft 365 Groups use a special AAD group type. But it is still a group in Azure Active Directory. A group in AAD is very similar to the old-school AD group in our on-premises directories. The group is made of members. In most on-premises cases these groups are managed by your directory admins. But also in AD you can specify owners of a group that will then be able to manage these groups… if they have the right tool (dsa.msc, …). In the cloud Microsoft (and myself) is pushing towards self-service for group management. This “self-service-first approach” is obvious since the introduction of now Office Microsoft 365 Groups. Teams being one of the most famous M365 Group services is also pushing to the owner/member model where end users are owners of a Team (therefor an AAD group). All end user facing UX from Microsoft is abstracting away how the underlying AAD group is managed. Teams for example will group owners and members in two sections: image

But if you look at the underlying AAD group you will find, that every owner is also a member:

image

And the Azure AD portal shows a dedicated section to manage owners of the group: image

The Microsoft Admin portal also has dedicated sections for members and owners: image

In general it is important that your administrative staff is aware how group membership (including ownership) is working. The problem with the Teams Admin portal as mentioned in the beginning is the result of this initial dialog leaving the group in an inconsistent state without making that admin aware of this misalignment. Lets check the group created in the initial screenshot using the Teams admin center. We specified 5 admin users and one member after the initial dialog. Non of the admin users was added as a member in AAD (Microsoft 365 Admin portal screenshot): image

But looking at the teams Admin center won’t show this “misconfiguration”: image

If Leia wants to access the associated Planner service for this Team/Group the following error will show: image

Planner is checking against group membership. Only is the user is a member Planner (and other services) will check if the user is also an owner and then show administrative controls. Teams itself looks ok. In SharePoint implemented a hack and the owners of the AAD group are granted Site Collection Admin permission so every item is accessible, because that is what Site Collection Admins are for. If your users are reporting problem like this and based on the Teams Admin center everything looks ok, go check “a better” portal like AAD.

To fix the problem in the Teams Admin center the owner has to be demoted to member state and then promoted to being a owner again.

A quick test with the SharePoint admin center shows that the system is managing membership as intended and every owner will also be a member. SharePoint requires one owner (Leia) in the first dialog and the second dialog allows additional owners and members: image

The result for members in AAD is correct as all owners are also added as members: image

Until this bug design flaw is fixed we recommend to not add more owners in the initial dialog. The currently logged-in admin user will be added as the only owner of the created group. In the next step remove your admin and add the requested users as members and promote them to owners. If you rely on self-service this should is not a problem. If you have a custom creation process automated hopefully you add all owner as member, but you should be good most of the time. We at glueckkanja-gab are supporting many customers with our custom lifecycle solution. We are adding a option to report this problem and a optional config switch to fix any detected misalignments.

I’ve created a UserVoice “idea” for this “bug”. So please lets go: vote!

https://microsoftteams.uservoice.com/forums/555103-public/suggestions/40951714-add-owners-also-as-members-aad-group-in-the-init

Daniel Schädler: Kubernetes Cluster erstellen mit Minikube

Ziel

Mein Ziel in diesem Beitrag ist, einen Kubernetes Cluster, mit meinem SetUp zu erstellen und mich mehr und mehr vertraut mit Containern und Kubernetes zu machen. Dieser Artikel beschreibt die Vorgehensweise mit meinem Setup und folgt dem Tutorial hier

Voraussetzungen

Damit ich einen Cluster installieren kann, muss zuerst Minikube installiert sein, welches eine leichtgewichtige Implementierung von Kubernetes darstellt. Dies kreiiert eine virutelle Machine (nachfolgend VM genannt) auf dem lokalen Computer.

Durchführung

In diesem Abschnitt gehe ich Schritt für Schritt das interaktive Tutorial lokal nach denselben Schritten durch.

Version und Starten von Minikube

Natürlich kann auch mit dem interaktiven Tutorial auf gearbeitet werden. Da ich nun mein eigenes Setup habe, starte ich zuerst die Powershell als Administrator und gehe wie folgt vor:

  1. Powershell als Administrator starten
  2. Minikube version anzeigen und starten
Minikube Version und Start mit Powershell

Zum Vergleich, im interaktiven Terminal sieht dass dan so aus:

Minikube Version und Start im interaktiven Terminal auf kubernetes.io

Cluster Version

Um mit Kubernetes zu interagieren wird das Kommandzeilen Werkzeug kubectl verwendet. Zuerst schauen wir uns ein paar Cluster informationen an. Zu diesem Zweck geben wir das Kommando

kubectl version
kubectl version mit Powershell

Die interaktive Konsole zeigt diese Informationen ein wenig übersichtlicher an.

kubectl version im interaktiven Terminal auf kubernetes.io

Cluster Details

Um die Cluster Details anzuzeigen geben wir folgende Kommandos ein:

kubectl cluster-info
kubectl get nodes

Mit dem Nodes-Kommando werden uns alle Nodes (Knoten) gezeigt die wir für unsere Applikationen verwenden können.

kubectl cluster und node informationen in der powershell

Das interaktive Terminal zeigt uns die Informationen wie folgt an:

kubectl cluster und node informationen im interaktiven Terminal von kubernetes.io

Fazit

Ich hätte nie gedacht, dass dies so einfach sein wird und bin mir bewusst, dass die Herausforderungen noch kommen werden. Nun freue ich mich auf die weiteren Module und werde diese mit dem Fokus einer .NET Applikation durcharbeiten. Weiter geht es dann mit dem nächsten Modul mit meiner Installation.

Daniel Schädler: Meine ersten Schritte mit Kubernetes /Minikube

Ziel

Das Ziel, ist es einen Minikube auf meinem Windows Rechner zu installieren um mich in die Welt von Container und Kubernetes einzuarbeiten. Das soll eine Artikelserie geben, die basierend auf den folgenden Tutorials ist Kubernetes.io

Voraussetzungen

Zuerst müssen die folgenden Voraussetzungen erfüllt sein, damit Minikube installiert werden kann:

  • kubectl muss installiert sein

Ich habe dies mit Powershell durchgeführt. Hierzu muss diese als Administrator ausgeführt werden. Anschliessend kann mit dem folgenden Befehl die Installation initiiert werden.

Install-Script Name 'install-kubectl' -Scope CurrentUser -Force
install-kubectl.ps1 -DownloadLocation "C:\Program Files\kubectl"

Es kann sein, dass der NuGet Provider, wie im nachfolgenden Bild, aktualisiert werden muss. Hier mit „Y“ bestätigen und den Befehl noch einmal ausführen, wie oben beschrieben.

Installation von Minikube mit Powershell

Ich selber hatte Probleme, die mit dem Bit-Filetransfer die Installation verhinderten. Damit ich trotzdem weiterkam, habe ich die Binary manuell heruntergeladen, diese in den „C:\Program Files\kubectl“ Ordner kopiert und den Pfad angegeben.

 Die Version ist mir mit folgendem Befehl angezeigt worden:

kubectl client --version
Kubectl Client version

Nun sind alle Voraussetzungen für die Minikube Installation, wie [hier](https://kubernetes.io/docs/tasks/tools/install-kubectl/) beschrieben, durchgeführt worden.

Durchführung

Für die Installation des Minikube, habe ich mich für den Windows Installer entschieden. Dieser muss dann als Administrator installiert werden.

Minikube Installation als Administrator

Minikube Setup

Ist die ausführbare Datei als Administrator gestartet worden, so werden die Installationsschritte durchgegangen:

  1. Als erstes wählen wir die Sprache "Englisch"
Setup Sprache wählen
  1. Anschliessend sind die Lizenvereinbarungen zu bestätigen.

  2. Danach den Installationsort wählen. Ich habe diesen auf dem Standardwert wie unten dargestellt belassen.

Installationsziel für Minikube

Minikube Installationbestätigung

Mein Setup ist Windows 10 mit Hyper-V. So muss Minikube gestartet werden. Eine Liste der Treiber kann hier gefunden werden.

Zu disem Zweck muss die Powershell wieder mit Administratorenrechten und dem folgenden Befehl ausgeführt werden:

minikube start --driver=hyperv

Ist alles korrekt eingegeben worden, dann startet Minikube und setzt seine Umgebung zur Verwendung mit Hyper-V auf, wie im nachfolgenden Bild dargestellt.

Minikube mit Hyper-V initialisieren

Fazit

So kann ich mir, ohne grosse Kosten auf meinem private Azure Portal einmal Kubernetes lokal in Verbindung mit Hyper-V anschauen und mich vertraut machen. In weiteren Artikeln werde ich mich analog den hier hier durchhangeln und mir dann später eine Umgebung aufzubauen, die in etwa bei den meisten Deployments vorkommt.

Don't contact us via this (fleischfalle@alphasierrapapa.com) email address.