Der Gedanke hinter dem PowerShell Script Framework (GitHub Projekt) ist, nur einen kleinen Teil des Scripts statisch auf dem System zu haben, der Rest soll dynamisch nachgeladen werden.
Durch dieses Vorgehen kann das je nach Bedarf ein nachgelagertes Script verändert oder sogar ganz neue Scripte hinzugefügt werden.
Im Detail sieht das Ganze dann so aus:
- Updater.ps1 wird gestartet
- Updater.ps1 lädt alle Files in seinem XML herunter
- Updater.ps1 startet das BaseScript.ps1 (in seinem XML Konfiguriert)
- BaseScript.ps1 startet alle Skripte in seinem XML nacheinander aus
Für den Download der weitere Daten ist ein WebServer von Nöten. Hier in meinem Beispiel verwende ich dazu diese WebSite, im Normalfall wird das aber ein interner IIS o.ä. sein.
Das Script Framework GitHub Projekt
GitHub: PS-ScriptFramework-AutoUpdate
Das Script Framework im Detail
Updater.ps1
<# .NOTES =========================================================================== Created by: Markus Kraus Organization: mycloudrevolution.com Personal Blog: mycloudrevolution.com Twitter: @vMarkus_K =========================================================================== .DESCRIPTION This is a self updating PowerShell Script Framework. Module-Name: Updater Module Version: 1.2 #> [CmdletBinding()] Param( [Parameter(Mandatory=$False,Position=1)] [string] $Config = "Default", [Parameter(Mandatory=$False,Position=2)] [string] $IgnoreUpdate = $False ) #region: Clear Errors $error.clear() #endregion #region: Global Definitions $Validate = $True $BaseDir = $env:windir + "\TEMP\PS\" $LogFile = $BaseDir + "Output.txt" $BaseURL = "http://mycloudrevolution.com/Projects/PS-Framework/" $BaseXML = "Updater.xml" $BasePS1 = "Updater.ps1" Start-Transcript -Path $LogFile Write-Output "`nStarting with Config: $Config" #endregion #region: Check amd Create Path if (!(Test-Path -path $BaseDir)) { try { New-Item -ItemType directory -Path $BaseDir } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Create $BaseDir" } } #endregion #region: Download BaseXML try { $Url = $BaseURL + $BaseXML Invoke-WebRequest $Url -OutFile ($BaseDir + $BaseXML) -ErrorAction Stop } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Download $BaseXML" } #endregion #region: Download BasePS1 try { $Url = $BaseURL + $BasePS1 Invoke-WebRequest $Url -OutFile ($BaseDir + $BasePS1) -ErrorAction Stop } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Download $BasePS1" } #endregion #region: Check and Load BaseXML $BaseXMLPath = $BaseDir + $BaseXML If (Test-Path $BaseXMLPath ) { try {$BaseXMLContent = [XML] (Get-Content $BaseXMLPath )} catch {$Validate = $false; Write-Error "`nERROR: Invalid $BaseXML"} } Else { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Cannot load $BaseXML" } #endregion #region: Download Files from BaseXML [Array] $BaseFiles = $BaseXMLContent.Updater.Files.File Foreach($BaseFile in $BaseFiles){ try { $Url = $BaseURL + $BaseFile Invoke-WebRequest $Url -OutFile ($BaseDir + $BaseFile) -ErrorAction Stop } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Download $BaseFile" } } #endregion #region: Download Customer Files if ($Config -ne "Default") { [Array] $ConfigFiles = @("Customer-" + $Config + ".xml"; "Customer-" + $Config + ".ps1") Foreach($ConfigFile in $ConfigFiles){ try { $Url = $BaseURL + $ConfigFile Invoke-WebRequest $Url -OutFile ($BaseDir + $ConfigFile) } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Download $ConfigFile" } } } #endregion #region: Updater Check $myFile = $MyInvocation.MyCommand.Path $myFileHash = (Get-FileHash -Path $myFile -Algorithm MD5).Hash $newFile = ($BaseDir + $BasePS1) $newFileHash = (Get-FileHash -Path $newFile -Algorithm MD5).Hash Write-Output "`nMy Updater File: $myFile `nMD5 Hash: $myFileHash" Write-Output "`nNew Updater File: $newFile `nMD5 Hash: $newFileHash" if ($myFileHash -ne $newFileHash -and $IgnoreUpdate -eq $False) { try { Copy-Item $newFile -Destination $($myFile) Write-Warning "Replacing local Updater.ps1 with Server Version. Exiting this Version and wait for next run..." Stop-Transcript Exit } catch { # Error out if replacing fails $Validate = $false Write-Error "`nERROR: Failed to Update Update.ps1" } } elseif ($myFileHash -ne $newFileHash -and $IgnoreUpdate -eq $true) { Write-Warning "Replacing local Updater.ps1 with Server Version skipped..." } elseif ($myFileHash -eq $newFileHash ) { Write-output "No Updater.ps1 update needed..." } #endregion #region: Start Base Script if($Validate -eq $True) { $BaseScript = ($BaseXMLContent.Updater.Variable | Where-Object {$_.Name -eq "BaseScript"}).Value Write-Output "`nStarting $BaseDir$BaseScript" Invoke-Expression ($BaseDir + $BaseScript) } else { Write-Warning "Starting $BaseDir$BaseScript skipped... Validation Error." } #endregion #region: Finalize Stop-Transcript if($Validate -eq $false) { Write-Error "`nERROR: Validation Error(s) occured during Script!" } #endregion
BaseScript.ps1
<# .NOTES =========================================================================== Created by: Markus Kraus Organization: mycloudrevolution.com Personal Blog: mycloudrevolution.com Twitter: @vMarkus_K =========================================================================== .DESCRIPTION This is a self updating PowerShell Script Framework. Module-Name: BaseScript Module Version: 1.1 #> #region 1: Global Definitions $Validate = $True $BaseScriptXML = "BaseScript.xml" #endregion #region 2: Check and Load BaseXML $BaseScriptXMLPath = $BaseDir + $BaseScriptXML If (Test-Path $BaseXMLPath ) { try {$BaseScriptXMLContent = [XML] (Get-Content $BaseScriptXMLPath )} catch {$Validate = $false; Write-Error "`nERROR: Invalid $BaseScriptXML"} } Else { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Cannot load $BaseScriptXML" } #endregion #region 3: Base Script Invokes if($Validate -eq $True) { [Array] $Scripts = $BaseScriptXMLContent.BaseScript.Scripts.Script Foreach($Script in $Scripts){ try { Write-Output "`nStarting $BaseDir$Script" Invoke-Expression ($BaseDir + $Script) } catch { # Error out if loading fails $Validate = $false Write-Error "`nERROR: Failed to Start $Script in $BaseDir" } } } #endregion #region 4: Finalize if($Validate -eq $false) { Write-Error "`nERROR: Validation Error(s) occured during Script!" } #endregion
Das Script Framework in der Praxis
Dieses Vorgehen wurde ursprünglich entwickelt um auf einer größeren Anzhal von Kundensystemen ein extern aktualisierbares Script zur Lizenzerfassung laufen zu lassen.
In diesem Praxis beispiel sieht die XML für das BaseScript z.B. so aus:
- Es wird als erstes die AD geprüft und vorbereitet
- Es werden dann die Exchange SALS erfasst
- Es werden final die Terminal Server User erfasst
Neue Versionen
17.08.2016
Updater.ps1 – Version 1.2
- Neu: Selbst Update über Hash des lokalen und remote Files
- Neu: Parameter zum deaktivieren des Selbst Update (-IgnoreUpdate:$True – Default ist $False)
myExample.ps1 – Version 1.1
- Verbesserung: Statt einem neuen Eventlog Eintrag wird ein Popup angezeigt
BaseScript.ps1 – Version 1.1
- Verbesserung: Nur Behebung von Schönheitsfehlern