EDD9ADFBC6E745A6AD7251F8A545020A
  • Thomas Pollinger
  • 30.05.2019
  • DE

SmartExtensions: Build-Process und Build-Pakete

Nun beschäftigen wir uns mit dem s.g. Build-Prozess und den daraus entstehenden Build-(Deployment)-Paketen. Mit dem Build-Prozess werden die Erweiterungen zusammengebaut und sichergestellt, dass die gewünschten Versionen, Konfigurationen und Inhalte im Paket enthalten sind. Damit jeder dies auch ohne größere Probleme selbst machen kann, habe ich lange überlegt und bin dann auf eine Lösung mit PowerShell gekommen...
 

Warum PowerShell?

PowerShell beschäftigt mich schon länger und mit der aktuellen Variante PowerShell Core bekommt man ein Skript-Console auf allen Plattformen. PowerShell, gegenüber allen anderen Varianten, hat für mich den Vorteil, dass man es überall ausführen kann und der Skript im Kern eine Art "PageBuilder" (wie im Management Server) ist.

Das PowerShell-Skript befolgt Anweiseungen für s.g. $BuildPackages, welche die Konfiguration jedes einzelnen Build-Paketes wiederspiegeln. Das Skript selbst in kompletter Form liegt im GitHub, jedoch möchte ich mich den wichtigen Abschnitten im Detail widmen.
 

Allgemeine Einstellungen

In den allgemeinen Einstellungen, nachfolgend $BuildSettings bezeichnet, werden die zentralen Bestandteile jedes $BuildPackages definiert.

$MasterPath = ("Path-2-Folder\SmartExtensions");

$BuildSettings = @{
    Version               = ("16.0.0");
    Build                 = 0;
    Compatibility         = ("11.2.2.0");
    Config                = ("config");
    Development           = ("development");
    Root                  = ("{0}" -f $MasterPath);
    RootArchive           = ("{0}\archive" -f $MasterPath);
    RootBackup            = ("{0}\backup" -f $MasterPath);
    RootExport            = ("{0}\build" -f $MasterPath);
    InstallerSource       = ("Import-ExtensionPackageManager.xml");
    InstallerFile         = ("Installer.xml");
    Libraries             = @{
        Bootstrap           = "4.3.1-dist";
        ClipboardJS         = "2.0.4";
        FontAwesome         = "5.8.1";
        Gijgo               = "1.9.6a";
        Handlebars          = "4.1.2";
        jQuery              = "3.4.0";
        jQueryScrollingTabs = "2.5.0";
        JQueryFreezeTable   = "1.2.0";
        JSCookie            = "2.2.0";
        Lodash              = "4.17.11";
        MomentJS            = "2.24.0";
        MomentJSMSDate      = "2.0.1";
        MomentJSTimezone    = "0.5.25-2019a";
    };
    Date                  = Get-Date;
    Excludes              = @(
        "uuid.txt",
        "_build.version",
        "includes"
    );
    Includes              = @(
        "head-meta",
        "head-favicon",
        "head-stylesheet-core-basics",
        "head-stylesheet-libraries-basics",
        "head-script-core-basics",
        "head-script-libraries-basics",
        "head-script-libraries-moment",
        "head-script-controller-basics",
        "body-loading",
        "body-no-support-ie",
        "dialog-head-label-controls",
        "modal-loading",
        "modal-about", 
        "modal-processing"
    );
    IncludePath           = ("includes");
    IncludeTargets        = @(
        "components",
        "config",
        "global",
        "embedded",
        "extensions"
    );
    IncludeFileExtensions = @(
        "html",
        "xml",
        "js",
        "css",
        "asp?"
    );
    IncludeSpecialFiles   = @(
        "libraries.htm",
        "license.htm"
    );
}

Der s.g. $MasterPath dient dazu, zentral zu definieren an welcher Stelle auf seinem System die Ordnerstruktur liegt. In der Regel wird hier der vollständige Pfad zum Ordner SmartExtensions angegeben.

Nachfolgend erkennt man, dass dieser zentrale Abschnitt sich um viele Dinge kümmert. Wie z.B. die Versionsnummer, die verwendeten Frameworkpaketversionen usw. Ich habe versucht die einzelnen Parameter so selbstsprechend wie möglich zu halten.
 

Paketdefinitionen

In den Paket-Definitionen ($BuildPackages) werden die zentralen $BuildSettings erstmal 1:1 übernommen. Je nach Paket werden diese dann entweder ersetzt, erweitert oder rausgenommen:

$BuildPackages = @(
    @{ #OWUG (Development & Testing)
        Name                  = "owug";
        PackageLabel          = " (owug)";
        PackageExtensions     = "Package-Extensions.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "embedded"
        );
        Includes              = $BuildSettings.Includes;
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    },
    @{ #OWUG-CUX2 (Development & Testing) incl. OpenText Common UX2.0
        Name                  = "owug-cux2";
        PackageLabel          = " (owug-cux2)";
        PackageExtensions     = "Package-Extensions.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "embedded"
        );
        Includes              = $BuildSettings.Includes + @(
            "head-stylesheet-theme-opentext-cux2-extension"
        );
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    },
    @{ #BETA (Ready for Production)
        Name                  = "beta";
        PackageLabel          = " (beta)";
        PackageExtensions     = "Package-Extensions.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "embedded",
            "extensions\cache-monitor",
            "extensions\Import-CacheMonitor.xml",
            "extensions\cluster-log-viewer",
            "extensions\Import-ClusterLogViewer.xml",
            "extensions\config-file-viewer",
            "extensions\Import-ConfigFileViewer.xml",
            "extensions\copy-metadata",
            "extensions\Import-CopyMetadata.xml",
            "extensions\delete-by-clipboard",
            "extensions\Import-DeleteByClipboard.xml",
            "extensions\publish-by-clipboard",
            "extensions\Import-PublishByClipboard.xml",
            "extensions\publishing-manager",
            "extensions\Import-PublishingManager.xml"
        );
        Includes              = $BuildSettings.Includes;
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    },
    @{ #BETA-CUX2 (Ready for Production) incl. OpenText Common UX2.0
        Name                  = "beta-cux2";
        PackageLabel          = " (beta-cux2)";
        PackageExtensions     = "Package-Extensions.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "embedded",
            "extensions\cache-monitor",
            "extensions\Import-CacheMonitor.xml",
            "extensions\cluster-log-viewer",
            "extensions\Import-ClusterLogViewer.xml",
            "extensions\config-file-viewer",
            "extensions\Import-ConfigFileViewer.xml",
            "extensions\copy-metadata",
            "extensions\Import-CopyMetadata.xml",
            "extensions\delete-by-clipboard",
            "extensions\Import-DeleteByClipboard.xml",
            "extensions\publish-by-clipboard",
            "extensions\Import-PublishByClipboard.xml",
            "extensions\publishing-manager",
            "extensions\Import-PublishingManager.xml"
        );
        Includes              = $BuildSettings.Includes + @(
            "head-stylesheet-theme-opentext-cux2-extension"
        );
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    }
    @{ #EMBEDDED (Ready for Production)
        Name                  = "embedded";
        PackageLabel          = " (embedded)";
        PackageExtensions     = "Package-Embedded.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "extensions"
        );
        Includes              = $BuildSettings.Includes;
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    },
    @{ #EMBEDDED-CUX2 (Ready for Production) incl. OpenText Common UX2.0
        Name                  = "embedded-cux2";
        PackageLabel          = " (embedded-cux2)";
        PackageExtensions     = "Package-Embedded.xml";
        PackageInformation    = "Package-Information.xml";
        Files                 = @();
        ImportXmlFiles        = @();
        Libraries             = $BuildSettings.Libraries;
        Date                  = $BuildSettings.Date;
        Version               = $BuildSettings.Version;
        Build                 = $BuildSettings.Build;
        Compatibility         = $BuildSettings.Compatibility;
        Config                = $BuildSettings.Config;
        Development           = $BuildSettings.Development;
        Root                  = $BuildSettings.Root;
        RootArchive           = $BuildSettings.RootArchive;
        RootExport            = $BuildSettings.RootExport;
        InstallerSource       = $BuildSettings.InstallerSource;
        InstallerFile         = $BuildSettings.InstallerFile;
        Excludes              = $BuildSettings.Excludes + @(
            "extensions"
        );
        Includes              = $BuildSettings.Includes + @(
            "head-stylesheet-theme-opentext-cux2-embedded"
        );
        IncludePath           = $BuildSettings.IncludePath;
        IncludeTargets        = $BuildSettings.IncludeTargets;
        IncludeFileExtensions = $BuildSettings.IncludeFileExtensions;
        IncludeSpecialFiles   = $BuildSettings.IncludeSpecialFiles;
        UUIDs                 = @();
    }
);

Somit entstehen einzelne Paketdefinitionen ($BuildPackages) welche dann vom PowerShell-Skript abgearbeitet werden.
 

Verarbeitung

In dem nachfolgenden Abschnitt, werden dann die einzelnen $BuildPackages verarbeitet. Dazu werden je $BuildPackage ein PowerShell-Job gestart und zuvor der entsprechende $ScriptBlock erzeugt :

Write-BuildMessage -Message ("Create configuration for build packages");

...

Write-Host ("`r`n`r`nCreate parallel jobs in the queue for each package:`r`n") -ForegroundColor ("yellow");

foreach ($BuildPackage in $BuildPackages) {

    Start-Sleep -Milliseconds 500;

    $Scriptblock = {

        param($BuildPackage)

        ...

    }

    Write-BuildMessage -Message ("Start job for build package .\{0}" -f $BuildPackage.Name);
    Start-Job -ScriptBlock $Scriptblock -Name $BuildPackage.Name -ArgumentList $BuildPackage | Out-Null;
    Write-BuildMessageState;

}

Innerhalb dieser Verarbeitung ($ScriptBlock) gibt es mehrere Schritte, welche jeweils im Kontext des entsprechenden $BuildPackage verarbeitet werden. Wenn man die Ausgabe der Informationen an die Console mal weglässt, dann gibt es die nachfolgenden Schritte:
 

Überprüfen und bereinigen des vorherigen Builds:

        Write-BuildMessage -Message ("Check and clean up previous build");
        if (Test-Path -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name)) {
            Remove-Item -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -Recurse -Force;
        }
        while (Test-Path -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name)) {
            Start-Sleep -Milliseconds 500;
        }
        Write-BuildMessageState;

 

Erstellen eines vollständigen Abbilds:

        Write-BuildMessage -Message ("Create a complete image of {0}" -f $BuildPackage.Development);
        Copy-Item -Path ("{0}\{1}" -f $BuildPackage.Root, $BuildPackage.Development) -Destination ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -Recurse;
        Write-BuildMessageState;

 

Bereinigung des Abbilds für die Auslieferung:

        if ($BuildPackage.Excludes.count -ne 0) {
            Write-BuildMessage -Message ("Clean up image .\{0} for customer delivery" -f $BuildPackage.Name);
            foreach ($Item in $BuildPackage.Excludes) {
                Remove-Item -Path ("{0}\{1}\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $Item) -Force -Recurse;
            }
            Write-BuildMessageState;
        }

 

Alle Dateien oder Verzeichnisse für den Build-Prozess finden:

        Write-BuildMessage -Message ("Find all files or directories for the build process");
        $AllDirectories = Get-ChildItem -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -Directory -Recurse;
        $AllFiles = Get-ChildItem -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -File -Recurse;
        Out-File -InputObject ($AllDirectories.FullName) -FilePath ("{0}\{1}\_list-directories.txt" -f $BuildPackage.RootExport, $BuildPackage.Name) -Force;
        Out-File -InputObject ($AllFiles.FullName) -FilePath ("{0}\{1}\_list-files.txt" -f $BuildPackage.RootExport, $BuildPackage.Name) -Force;
        Write-BuildMessageState;

 

Ermitteln aller Dateien, die innerhalb des Build-Prozesses geändert werden sollen:

        Write-BuildMessage -Message ("Find all files for modification within the build process");
        foreach ($Target in $BuildPackage.IncludeTargets) {
            foreach ($Extension in $BuildPackage.IncludeFileExtensions) {
                $Results = Get-ChildItem -Path ("{0}\{1}\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $Target) -Filter ("*.{0}" -f $Extension) -Exclude ("*.min.{0}" -f $Extension) -Recurse;
                $BuildPackage.Files += $Results;
                if ($Extension -eq "xml" -and $Target -ne "config") {
                    $BuildPackage.ImportXmlFiles += $Results;
                }
            }
        }
        Write-BuildMessageState;

 

Ermitteln aller speziellen Dateien zur Änderung innerhalb des Build-Prozesses:

        Write-BuildMessage -Message ("v");
        foreach ($SpecialFile in $BuildPackage.IncludeSpecialFiles) {
            $Results = Get-ChildItem -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -Filter ("{0}" -f $SpecialFile) -Recurse;
            $BuildPackage.Files += $Results;
        }
        Write-BuildMessageState;

 

Alle Fragmente und Werte in die Zieldateien einfügen:

        Write-BuildMessage -Message ("Inject all fragments & values into the ({0}) target files" -f $BuildPackage.Files.Count);
        $OutfileUUID = ("");
        $OutfileNonUUID = ("");
        foreach ($File in $BuildPackage.Files) {
            $FileContent = Get-Content -Path $File.FullName;
            $FileContentRaw = $FileContent | Out-String;
            $Result = @{ UUID = ($FileContent | Where-Object { $_ -match "File UUID: " }); File = ($File.FullName); };
            if ($Result.UUID) {
                $Result.UUID = $Result.UUID.Substring($Result.UUID.Length - 36);
                $BuildPackage.UUIDs += $Result;
                $OutfileUUID += ("{0} => {1}`r`n" -f $Result.UUID, $Result.File);
            }
            else {
                $OutfileNonUUID += ("N/A => {0}`r`n" -f $File.FullName);
            }
            foreach ($Include in $BuildPackage.Includes) {
                $FileContentRaw = $FileContentRaw -replace (("<!-- Build-Include: {0} -->" -f $Include), (Get-Content -Path ("{0}\{1}\{2}\{3}.htm" -f $BuildPackage.Root, $BuildPackage.Development, $BuildPackage.IncludePath, $Include) -Raw));
            }
            foreach ($Library in $BuildPackage.Libraries.GetEnumerator()) {
                $SearchString = "{" + ("build-library-{0}-release" -f $Library.Name.ToLower()) + "}";
                $FileContentRaw = $FileContentRaw -replace ($SearchString, $Library.Value);
            }
            $FileContentRaw = $FileContentRaw -replace ("\s*(<\/include>|<include>)\s");
            $FileContentRaw = $FileContentRaw -replace ("{build-release}", ("{0}.{1}" -f $BuildPackage.Version, $BuildPackage.Build));
            $FileContentRaw = $FileContentRaw -replace ("{build-package}", $BuildPackage.Name);
            $FileContentRaw = $FileContentRaw -replace ("{build-package-label}", $BuildPackage.PackageLabel);
            $FileContentRaw = $FileContentRaw -replace ("{build-package-extensions}", $BuildPackage.PackageExtensions);
            $FileContentRaw = $FileContentRaw -replace ("{build-package-information}", $BuildPackage.PackageInformation);
            $FileContentRaw = $FileContentRaw -replace ("{build-compatibility}", $BuildPackage.Compatibility);
            $FileContentRaw = $FileContentRaw -replace ("{build-date}", (Get-Date -Date $BuildPackage.Date -Format ("dd.MM.yyyy HH:mm:ss")));
            $FileContentRaw = $FileContentRaw -replace ("{build-copyright-date}", (Get-Date -Date $BuildPackage.Date -Format ("yyyy")));
            if ($File.Name -match ".xml") {
                Set-Content -Value $FileContentRaw -Path $File.FullName -NoNewline -Force -Encoding ("unicode");
            }
            else {
                Set-Content -Value $FileContentRaw -Path $File.FullName -NoNewline -Force;
            }
        }
        Write-BuildMessageState;

 

Installations- bzw. Import-XML erzeugen:

        if (Test-Path -Path ("{0}\{1}\extensions\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $InstallerSource.InstallerSource)) {
            Write-BuildMessage -Message ("Create the {0} for {1}" -f $BuildPackage.InstallerFile, $BuildPackage.Name);
            Copy-Item -Path ("{0}\{1}\extensions\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $BuildPackage.InstallerSource) -Destination ("{0}\{1}\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $BuildPackage.InstallerFile) -Recurse -Force;
            Write-BuildMessageState;
        }

 

Erzeugen des Pakets:

        Write-BuildMessage -Message ("Create the {0} bundle for {1}" -f $BuildPackage.PackageExtensions, $BuildPackage.Name);
        foreach ($ImportXmlFile in $BuildPackage.ImportXmlFiles) {
            $FileContentRaw = (Get-Content -Path $ImportXmlFile.FullName -Raw);
            $ImportXmlPackage += $FileContentRaw;
        }
        Set-Content -Value ($ImportXmlPackage -replace ("\s*</plugins>\s*<plugins>") -replace ("</plugins>\s*", ("</plugins>").ToUpper())) -Path ("{0}\{1}\config\{2}" -f $BuildPackage.RootExport, $BuildPackage.Name, $BuildPackage.PackageExtensions) -NoNewline -Force -Encoding ("unicode");
        Write-BuildMessageState;

 

Dateiliste für UUID jeder Datei erstellen:

        Write-BuildMessage -Message ("Create file list for UUID of each file");
        Out-File -InputObject $OutfileUUID -FilePath ("{0}\{1}\_list-files-uuid.txt" -f $BuildPackage.RootExport, $BuildPackage.Name) -NoNewline -Force;
        Out-File -InputObject $OutfileNonUUID -FilePath ("{0}\{1}\_list-files-without-uuid.txt" -f $BuildPackage.RootExport, $BuildPackage.Name) -NoNewline -Force;
        Write-BuildMessageState;

 

Überprüfen auf eindeutige Identifikatoren in den identifizierten Dateien:

        Write-BuildMessage -Message ("Check for unique identifiers in the identified files");
        $ErrorState = $false;
        $Results = $BuildPackage.UUIDs | Group-Object { $_.UUID } | Where-Object { $_.Count -gt 1 };
        if ($Results.Count -gt 1) {
            $ErrorState = $true;
        }
        Write-BuildMessageState -ErrorState ($ErrorState);

        # Error Output
        if ($ErrorState) {
            foreach ($Result in $Results) {
                Write-Host ("   > UUID: {0}" -f $Result.Name) -ForegroundColor ("darkcyan");
                $Result.Group.File | ForEach-Object { Write-Host ("     > {0}" -f $_) -ForegroundColor ("darkcyan"); };
            }
        }

 

Überprüfung auf Zero-Byte-Dateien nach dem Build-Prozess:

        Write-BuildMessage -Message ("Check for zero byte files after build process");
        $Results = $AllFiles | Where-Object { $_.Length -eq 0 };
        if ($Results.Count -ne 0) {
            Out-File -InputObject ($Results.FullName) -FilePath ("{0}\{1}\_list-zerobytefiles.txt" -f $BuildPackage.RootExport, $BuildPackage.Name) -Force;
            $ErrorState = $true;
        }
        else {
            $ErrorState = $false;
        }
        Write-BuildMessageState -ErrorState ($ErrorState);

        # Error Output
        if ($ErrorState) {
            foreach ($Result in $Results) {
                Write-Host ("   > {0}" -f $Result.Name) -ForegroundColor ("darkcyan");
            }
        }

 

Erstellen von Paket-, Erstellungs- und Versionsdaten:

        Write-BuildMessage -Message ("Create package, creation and version data");
        Export-Clixml -InputObject $BuildPackage.Date -Path ("{0}\{1}\_build.creation" -f $BuildPackage.RootExport, $BuildPackage.Name) -Force;
        Export-Clixml -InputObject $BuildPackage.Build -Path ("{0}\{1}\_build.version" -f $BuildPackage.RootExport, $BuildPackage.Name) -Force;
        Write-BuildMessageState;

 

Bestimmen aller Elemente für die endgültige Paketerstellung:

        Write-BuildMessage -Message ("Determine all elements for the final package creation");
        $AllItems = Get-ChildItem -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -Recurse;
        Write-BuildMessageState;

 

Setzen des Erstellungsdatums für das gesamte Paket:

        Write-BuildMessage -Message ("Set the creation date for the entire package");
        $ErrorState = $false;
        try {
            foreach ($Item in $AllItems) {
                $Item.LastWriteTime = $BuildPackage.Date;
            }
        }
        catch {
            $ErrorState = $true;
        }
        Write-BuildMessageState -ErrorState ($ErrorState);

 

Erstellung eines komprimierten Archivs des gesamten Build:

        Write-BuildMessage -Message ("Create a compressed archive of the complete build");
        $global:ProgressPreference = ("SilentlyContinue");
        Compress-Archive -Path ("{0}\{1}" -f $BuildPackage.RootExport, $BuildPackage.Name) -DestinationPath ("{0}\build-{1}-{2}.{3}.zip" -f $BuildPackage.RootArchive, $BuildPackage.Name, $BuildPackage.Version, $BuildPackage.Build) -CompressionLevel Optimal -Force;
        $global:ProgressPreference = ("Continue");
        Write-BuildMessageState;

 

Mitteilung der Ablagepfades und weiterer Build-Prozess-Infos:

        Write-BuildMessage -Message ("Created build is available here .\{0} [{1}.{2}]" -f $BuildPackage.Name, $BuildPackage.Version, $BuildPackage.Build);
        Write-BuildMessageState;
        $TimeBuildFinished = Get-Date;
        Write-Host ("`r`n -> Package ({0}) was built in {1} seconds with {2} items.`r`n" -f $BuildPackage.Name, ($TimeBuildFinished - $TimeBuildStart).TotalSeconds, $AllItems.Count) -ForegroundColor ("cyan");

 

Allgemeine Prozess-Aktionen vor und nach dem Build-Prozess

Es gibt auch noch weitere Schritte, welche vor und nach dem Build-Prozess ausgeführt werden.

Allgemeine Infos:

$TimeScriptStart = Get-Date;

Write-Host ("`r`nBuild process is running.`r`n") -ForegroundColor ("red");

Write-Host ("`r`nCommon actions:`r`n") -ForegroundColor ("yellow");

 

Lädt den aktuellen zentralen Build-Zähler von der Festplatte und zählt aufwärts:

Write-BuildMessage -Message ("Load the current central build counter from disk and count up");
if (Test-Path -Path ("{0}\{1}\_build.version" -f $BuildSettings.Root, $BuildSettings.Development)) {
    $BuildSettings.Build = Import-Clixml -Path ("{0}\{1}\_build.version" -f $BuildSettings.Root, $BuildSettings.Development);
    $BuildSettings.Build++;
}
else {
    Export-Clixml -InputObject $BuildSettings.Build -Path ("{0}\{1}\_build.version" -f $BuildSettings.Root, $BuildSettings.Development) -Force;
    $BuildSettings.Build++;
}
Write-BuildMessageState;

 

Warten auf die Antwort von jedem Build-Auftrag:

Write-BuildMessage -Message ("Waiting for response from each build job");
while (Get-Job -State ("Running")) {
    Start-Sleep -Milliseconds 1000;
}
Write-BuildMessageState;

 

Konsolenausgabe von jedem Build-Auftrag erhalten:

Write-BuildMessage -Message ("Get console output from each build job");
Write-BuildMessageState;
Write-Host ("`r`n") -NoNewline;
Get-Job | Receive-Job;

 

Bereinigung der Warteschlange für Build-Aufträge:

Write-BuildMessage -Message ("Clean up build job queue");
Get-Job -State ("Complete") | Remove-Job;
Write-BuildMessageState;

 

Speichern des aktuellen zentralen Build-Zählers auf der Festplatte:

Write-BuildMessage -Message ("Save the current central build counter to disk");
Export-Clixml -InputObject $BuildSettings.Build -Path ("{0}\{1}\_build.version" -f $BuildSettings.Root, $BuildSettings.Development) -Force;
Write-BuildMessageState;

 

Backup der Entwicklung erstellen:

Write-BuildMessage -Message ("Create backup of the development");
$global:ProgressPreference = ("SilentlyContinue");
Compress-Archive -Path ("{0}\{1}" -f $BuildSettings.Root, $BuildSettings.Development) -DestinationPath ("{0}\backup-{1}-{2}.{3}.zip" -f $BuildSettings.RootBackup, $BuildSettings.Development, $BuildSettings.Version, $BuildSettings.Build) -CompressionLevel Optimal -Force;
$global:ProgressPreference = ("Continue");
Write-BuildMessageState;

 

Allgemein Informationen über den kompletten Ablauf:

$TimeScriptFinished = Get-Date;
Write-Host ("`r`n -> The complete runtime for build {0}.{1} was {2} for {3} packages.`r`n" -f $BuildSettings.Version, $BuildSettings.Build, ($TimeScriptFinished - $TimeScriptStart).TotalSeconds, $BuildPackages.Count) -ForegroundColor ("cyan");

Write-Host ("`r`nBuild process is completed`r`n") -ForegroundColor ("red");

 

Ausgabe in der Console zur Laufzeit

Wenn man nun den PowerShell-Skript ausführt, wird diese Ausgabe erzeugt:

PS C:\..\SmartExtensions> .\Start-BuildProcessExtensions.ps1

Build process is running.


Common actions:

 - Load the current central build counter from disk and count up           [PASSED]
 - Create configuration for build packages                                 [PASSED]


Create parallel jobs in the queue for each package:

 - Start job for build package .\owug                                      [PASSED]
 - Start job for build package .\owug-cux2                                 [PASSED]
 - Start job for build package .\beta                                      [PASSED]
 - Start job for build package .\beta-cux2                                 [PASSED]
 - Start job for build package .\embedded                                  [PASSED]
 - Start job for build package .\embedded-cux2                             [PASSED]

 -> Running for build 16.0.0.1210

 - Waiting for response from each build job                                [PASSED]
 - Get console output from each build job                                  [PASSED]


Packaging actions for owug:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\owug for customer delivery                             [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (87) target files                [PASSED]
 - Create the Installer.xml for owug                                       [PASSED]
 - Create the Package-Extensions.xml bundle for owug                       [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\owug [16.0.0.1255]                    [PASSED]

 -> Package (owug) was built in 16,1705677 seconds with 408 items.


Packaging actions for owug-cux2:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\owug-cux2 for customer delivery                        [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (87) target files                [PASSED]
 - Create the Installer.xml for owug-cux2                                  [PASSED]
 - Create the Package-Extensions.xml bundle for owug-cux2                  [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\owug-cux2 [16.0.0.1255]               [PASSED]

 -> Package (owug-cux2) was built in 17,2361416 seconds with 408 items.


Packaging actions for beta:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\beta for customer delivery                             [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (64) target files                [PASSED]
 - Create the Installer.xml for beta                                       [PASSED]
 - Create the Package-Extensions.xml bundle for beta                       [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\beta [16.0.0.1210]                    [PASSED]

 -> Package (beta) was built in 15,2855219 seconds with 334 items.


Packaging actions for beta-cux2:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\beta-cux2 for customer delivery                        [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (64) target files                [PASSED]
 - Create the Installer.xml for beta-cux2                                  [PASSED]
 - Create the Package-Extensions.xml bundle for beta-cux2                  [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\beta-cux2 [16.0.0.1255]               [PASSED]

 -> Package (beta-cux2) was built in 15,30344 seconds with 334 items.


Packaging actions for embedded:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\embedded for customer delivery                         [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (32) target files                [PASSED]
 - Create the Package-Embedded.xml bundle for embedded                     [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\embedded [16.0.0.1255]                [PASSED]

 -> Package (embedded) was built in 12,2759317 seconds with 245 items.


Packaging actions for embedded-cux2:

 - Check and clean up previous build                                       [PASSED]
 - Create a complete image of development                                  [PASSED]
 - Clean up image .\embedded-cux2 for customer delivery                    [PASSED]
 - Find all files or directories for the build process                     [PASSED]
 - Find all files for modification within the build process                [PASSED]
 - Find all special files for modification within the build process        [PASSED]
 - Inject all fragments & values into the (32) target files                [PASSED]
 - Create the Package-Embedded.xml bundle for embedded-cux2                [PASSED]
 - Create file list for UUID of each file                                  [PASSED]
 - Check for unique identifiers in the identified files                    [PASSED]
 - Check for zero byte files after build process                           [PASSED]
 - Create package, creation and version data                               [PASSED]
 - Determine all elements for the final package creation                   [PASSED]
 - Set the creation date for the entire package                            [PASSED]
 - Create a compressed archive of the complete build                       [PASSED]
 - Created build is available here .\embedded-cux2 [16.0.0.1210]           [PASSED]

 -> Package (embedded-cux2) was built in 12,1867234 seconds with 245 items.


Common actions:

 - Clean up build job queue                                                [PASSED]
 - Save the current central build counter to disk                          [PASSED]
 - Create backup of the development                                        [PASSED]

 -> The complete runtime for build 16.0.0.1210 was 21,8309987 for 6 packages.


Build process is completed

PS C:\..\SmartExtensions>

Sollte es zu Fehlern kommen, wird das direkt ersichtlich und entsprechend als Fehlermeldung ausgegeben.  Die komplette Ausgabe wird entsprechend farbig in die Konsole geschrieben, hier ein Ausschnitt davon:

 

Build-Pakete

Wenn der PowerShell-Build-Skript seine Arbeit getan hat, dann sind entsprechend der Konfiguration die fertigen Build-Pakete als ZIP erzeugt worden. Diese kann man nun 1:1 auf dem Management-Server in das Verzeichnis ..\ASP\PlugIns\{Paketname} entpacken. Dann den Package-Manager als Plug-In importieren und direkt loslegen. Denn in jedem Paket ist alles notwendige dabei, was man für die Verwendung der Erweiterungen benötigt.

 

Ersetzungslogik

Während des Build-Prozesses werden in den Dateien zwei Arten von Ersetzugn vorgenommen:

Variablen

{build-...}

Diese spiegeln in der Regel Versionsnummer oder Pfade etc wieder.

 

Includes:

<!-- Build-Include: ... -->

Diese Includes entsprechen den Dateien (Fragmenten) im Ordner /includes und werden als komplette Blöcke an die entsprechenden Stellen eingefügt. Damit man jedes Include auch von normalen HTML-Dateien unterscheiden und auch innerhalb des Markups richtig positionieren kann. Haben die Includes einen Pseudo-Tag <include></include> welcher während des einfügens entfernt wird. Hier mal ein Beispiel:

<include>
    <!-- Loading: Default spinner -->
    <div class="spinner" id="idMainSpinner">
        <div class="spinner-grow text-danger" role="status"></div>
    </div>
</include>

Das Format wurde nach langem Testen und ausprobieren so gewählt. Da es mit einem HTML-Kommentar oder der o.g. Schreibweise keinerlei Probleme gab.


Viel Spaß beim ausprobieren, der Source-Code liegt bereits im GitHub zur freien Verfügung.

Im nächsten Artikel beschäftigen wir uns mit SmartExtensions: SmartEdit-Feature ;)


Über den Autor:
Thomas Pollinger

... ist Senior Site Reliability Engineer bei der Vodafone GmbH in Düsseldorf. Seit dem Jahr 2007 betreut er zusammen mit seinen Kollegen die OpenText- (vormals RedDot-) Plattform Web Site Management für die deutsche Konzernzentrale.

Er entwickelt Erweiterungen in Form von Plug-Ins und PowerShell Skripten. Seit den Anfängen in 2001 (RedDot CMS 4.0) kennt er sich speziell mit der Arbeitweise und den Funktionen des Management Server aus.