Archiv der Kategorie: PowerShell

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

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

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

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
}

Dateien rekursiv kopieren

Heute ist wieder mal der Tag an dem Powershell und C# einander ein wenig den Rang ablaufen (mir ist klar dass die Cmdlet’s in PowerShell in C# geschrieben sind, aber es verblüfft doch immer wieder wie einfach Sachen in PowerShell erledigt werden können.

Ziel eine Website mit Unterverzeichnissen kopieren, aber die svn-Dateien (SubVersion) weglassen. Den C# Code erspare ich mal, aber als Hinweis der MSDN-Link zu diesem Thema.

Mit PowerShell ein klassischer Ein- /Zwei- Zeiler (Je nach Bildschirm-Grösse) smile_wink

   1: Copy-Item -path C:\_Dev\AIS\Buraut.Ais.WebFrontEnd\src\WebSites 
   2: -destination C:\Temp\Test_Release -recurse

Wenn man dann auch entsprechende Unterverzeichnisse nicht wünscht, können diese wie folgt gelöscht werden.

   1: foreach($a in get-childitem C:\Temp\Test_Release -recurse -include "*.svn"){
   2:     $a | Remove-item -recurse -force
   3: }

Danach sind die entsprechenden Ordner nicht mehr vorhanden.

Wenn man mehrere Order haben möchte, dann muss man diese mit Semikolon einfach dran hängen und die Ordner, sowie deren Inhalt werden gelöscht. Ich wünsche allen ein erholsames Wochenende.

PowerShell XML Dateien in anderes Format speichern

Letztens hatte ich den Task 15 XML-Dateien so schnell wie möglich vom UTF8 Zeichenformat ins Unicode Zeichenformat zu konvertieren. Bis anhin hatte man die Dateien immer aufgemacht, Speichern unter gewählt und dann das entsprechende Format selektiert und dann das File in das entsprechende Zielverzeichnis gespeichert.

Mehr oder weniger konnte ich diesen Task automatisieren. Hier noch das kleine PowerShell Script welches die Dateien aus einem Input Ordner einliest und diese dann im Output Ordner im entsprechenden Format speichert.

   1: $inputPath = Read-Host "Bitte Quellpfad angeben"
   2: $ouputPath = Read-Host "Bitte Zielpfad angeben"
   3:  
   4: foreach($item in get-childItem $inputPath){
   5:    $file = get-content -path $inputpath\$item
   6:    out-file -filePath $ouputPath\$item -encoding Unicode -inputObject $file
   7: }

Einfach und schnell.