cruisinit

Sample work, IT tools self-made, IT tools usage. There's more to collect and publish. PGP Fingerprint 3E2A EC6D A0D4 214B EC3E 6593 4AC3 784F D6A4 25F4

# ApplicationBasisMonitor.ps1. Monitoring activity status of an application: host ping, check log write, check output. To be run from distant host. Audience: Informed IT basis. # A Scheduled Task trigger interval, if you want unattended runs, should adapt to the timespan here. #region Literals [string]$strApplicationHostIP = “192.168.0.0” [string]$strApplicationLog = “\server\share\Log” [string]$strApplicationOutput = “\server\share\output” [string]$strTransferShare = “\server2\share\” # [int]$intInterval = 2 # hours [string]$strSmtp = “smtp.org.org” [string[]]$strMailTo = “me@org.org”, “servicedesk@org.org” [string]$strMailFrom = “me@org.org” [string]$strMailSubject = “Org: Application: Basis monitor alert.” #endregion #region Body [boolean]$blnSuccess = $True $objResults = New-Object PSObject $objResults | Add-Member “Test time” (Get-Date).ToString() $objResults | Add-Member “HostPing” (Test-Connection $strApplicationHostIP) if (Test-Connection -Quiet -Count 1 $strApplicationHostIP) { $objResults | Add-Member “Ping” “OK” } else { $objResults | Add-Member “Ping” “Failed” $blnSuccess = $false } [DateTime]$datLastLogWrite = (dir $strApplicationLog | Sort-Object LastWriteTime -Descending | select -First 1).LastWriteTime $objResults | Add-Member “LastLogWrite” $datLastLogWrite if ((get-date).AddHours(-2) -gt $datLastLogWrite) { $objResults | Add-Member “Log” “no write for 2 hours” $blnSuccess = $false } else { $objResults | Add-Member “Log” “Active in last 2 hours” } [DateTime]$datLastOutputWrite = (dir $strApplicationOutput | Sort-Object LastWriteTime -Descending | select -First 1).LastWriteTime $objResults | Add-Member “LastOutputWrite” $datLastOutputWrite if ((get-date).AddHours(-2) -gt $datLastOutputWrite) { $objResults | Add-Member “Output” “no write for 2 hours” $blnSuccess = $false } else { $objResults | Add-Member “Output” “Active in last 2 hours” } [DateTime]$datLastTransferWrite = (dir $strTransferShare -Recurse | Sort-Object LastWriteTime -Descending | select -First 1).LastWriteTime $objResults | Add-Member “LastTransferWrite” $datLastTransferWrite if ((get-date).AddHours(-2) -gt $datLastTransferWrite) { $objResults | Add-Member “Transfer” “no write for 2 hours” $blnSuccess = $false } else { $objResults | Add-Member “Transfer” “Active in last 2 hours” } if ($blnSuccess -eq $false) { $strMailSubject = “Application inactive? “ + $strMailSubject } Send-MailMessage -SmtpServer $strSmtp ` -From $strMailFrom ` -Subject $strMailSubject ` -body ($objResults | Out-String) ` -To $strMailTo #endregion

# 20190319 Log4net-Monitor.ps1 # Sends a mail when a search term is logged in log4net logs. Can e.g. be run as a Scheduled Task. Logs hits to not repeatedly alert same log entries. # Understand the script before you use it. param ( [string]$AppLogFile = “C:\temp\logs\server\app.log”, [string]$SearchTerm = ”.SQLException” ) $Error.Clear() [string]$strLogFile = $PSCommandPath + ”.log” $Body = $null if ( (Test-Path $strLogFile) -eq $false ) { Set-Content -Path $strLogFile -Value “LogMonitorrn____________rn” } [boolean]$blnNewEntries = $false if ( (Test-Path $AppLogFile) -eq $false ) { Write-Error “No log file to parse, $AppLogFile was given.” } else { [string]$strAppLogFile = $AppLogFile + ”.tmp” Copy-Item $AppLogFile $strAppLogFile [string[]]$astrLogContent = ([io.file]::ReadAllText($strAppLogFile)).Replace(rn “,” “).Replace(rn([a-z,A-Z])”,” $2”) -split(rn”) -match($SearchTerm) [string[]]$astrEntriesAlreadySent = Get-Content $strLogFile for ($i = 0;$i -lt $astrLogContent.count; $i++) { [string[]]$astrRow = $astrLogContent[$i].Split(“[“) [string]$strTime = $astrRow[0].Trim() if ($strTime -in $astrEntriesAlreadySent -or $strTime -notlike '20*' ) { $astrLogContent[$i] = ”(Deleted)” } else { $blnNewEntries = $true Add-Content $strLogFile -Value $strTime } } switch ($blnNewEntries) { $true { [string]$Body = ($astrLogContent -notmatch(”(Deleted)”) | Sort-Object) -join(rn”) } $false { [string]$Body = “No new entries for search term.” } } } if ($error -ne $null) { $Body += rnrnErrorsrn” $Body += ($Error | Select-Object *) } $Body += rnrnLog entries sentrn________rn” [string[]]$astrParseLogSentWholeStory = Get-Content $strLogFile $Body += ($astrParseLogSentWholeStory -match '20*') -join(rn”) if ($blnNewEntries -or ($Error -ne $null)) { Send-MailMessage -From me@org.org -To me@org.org -Body $Body -SmtpServer 192.168.22.11 -Subject “New $SearchTerm or script error on $env:COMPUTERNAME in $AppLogFile” }

# Monitor-hosts.ps1. Best run as Scheduled Task at machine startup and repeated every 15 min. Is intended to detect changes even when you have not been online when your hosts has been tampered with. # ex.: <Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command> # <Arguments>-NoExit -NoProfile -ExecutionPolicy ByPass -File C:\util\Monitor-hosts.ps1</Arguments> [string]$strHostsLastPath = “c:\util\hosts.$env:COMPUTERNAME.last.xml” # Ensure directory for hash store file if ( ( Test-Path (Split-Path $strHostsLastPath -Parent) ) -eq $false ) { mkdir (Split-Path $strHostsLastPath -Parent) } if ( (Test-Path $strHostsLastPath) -eq $false ) # (Re) Initialize hash store file { (Get-WmiObject Win32_OperatingSystem).Version (Get-Hash C:\Windows\System32\drivers\etc\hosts -Algorithm SHA512).ToString() | Export-Clixml $strHostsLastPath # Get-Hash mit Community Extension in Win 7, Get-FileHash in Windows 10. Win 8? Set-ItemProperty $strHostsLastPath -Name IsReadOnly -Value $true } else # compare stored hash with current { [string]$strHashNow = (Get-Hash C:\Windows\System32\drivers\etc\hosts -Algorithm SHA512).ToString() # Get-Hash mit Community Extension in Win 7, Get-FileHash in Windows 10. Win 8? [xml]$xmlHashLast = gc $strHostsLastPath if ($xmlHashLast.Objs.S -ne $strHashNow) { Add-Type -AssemblyName System.Windows.Forms [string]$strMsg = “Check hosts, if you haven't edited hosts yourself in the last 15 min. Most likely hosts' hash has changed. If so, restore your hosts from backup and delete $strHostsLastPath (read-only) to reset this monitor.” [string]$strTitle = “hosts monitor” $void = [System.Windows.Forms.MessageBox]::Show($strMsg,$strTitle,[System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Exclamation) } else { “Hashs ident” } }

Automates upgrading a .NET and ASP.NET web application on IIS with MSSQL backend and .NET client installers. Chooses correct deployment packages, starts data model upgrade, builds a new web.config with the new settings from deployment packages and by selectively copying the installation specific, client side settings from the old files by attrib name.

#Requires -Version 4 #Requires -RunAsAdmin

# Todo: $strNewVersion, zunaechst aus .msi-Props ausgelesen, aus dann entpackter Anwendung -> bin\ -> .dll neu auslesen, falls nicht zueinander passende Deploymentpakete .zip und .msi zusammenkopiert wurden [string]$strNewVersion = ((Get-ItemProperty ($strNewVerFolder + “bin&lt;filename>.dll”)).VersionInfo).ProductVersion fuehrt aktuell zu Mosern ueber mangelnde Zugriffsrechte # Todo: Optische Aufbereitung des Mailbodys oder Attachments ueber CC-Webservice

<# .SYNOPSIS Update <Program> Standard. Automates upgrading a .NET and ASP.NET web application on IIS with MSSQL backend and .NET client installers. Chooses correct deployment packages, starts data model upgrade, builds a new web.config with the new settings from deployment packages and by selectively copying the installation specific, client side settings from the old files by attrib name. .DESCRIPTION Update <Program> Standard Syntax: [D:\Websites\Deploy\UpdaterScript]ProgramUpdaterAsAdmin.lnk (with a script file path in the command line saying D:\Websites\Deploy\UpdaterScript\ProgramUpdater.ps1. The default paths (see below) to both .INIs will usually work. If you need to supply an absolute path to one or boths .INIs, edit ProgramUpdaterAsAdmin.lnk's command line along this syntax note: D:\Websites\Deploy\UpdaterScript\ProgramUpdater.ps1 [[-IniFileUser] <String>] [[-IniFileWebConfig] <String>] [<CommonParameters>] ) Use this internal helper at your own risk. Addressed audience are informed IT workers. The author cannot be held liable for loss of data by the use of this script. * This script requires PowerShell version 4 (in WMF, but if you need update anyway, install WMF 5, https://www.microsoft.com/en-us/download/confirmation.aspx?id=54616&6B49FDFB-8E5B-4B07-BC31-15695C5A2143=1). * This script requires to be run as admin. * You may supply absolute paths inc. file name to the two .ini files as the only parameter. Use “ ” if path contains blanks. * Run without parameter, this script searches its .ini at D:\Websites\Deploy\UpdaterScript\ProgramUpdaterSettingsuser.ini plus D:\Websites\Deploy\UpdaterScript\ProgramUpdaterSettingsweb.config.ini. (In fact, this script retrieves its own full path and replaces “.ps1” with “Settingsuser.ini” / “Settingsweb.config.ini”, and this will regularly lead to the paths mentioned above.) * Your website folder path in Settingsuser.ini must start with “D:\Websites\“. If it does not, the script adds this automatically. * Your website folder path in Settingsuser.ini must end with “\Company.Program.Gui\“. If it does not, the script adds this automatically. * Deployment folder is always D:\websites\deploy. Copy your deployment packages (.ZIPs, .MSIs, UpgradeTool.exe) into this folder. * The current default for the MS SQL version (part of deployment packages' names) is 2008 (means: 2008 and 2008 R2). You can define other versions (allowed: 2012, 2014) by machine name in Settingsuser.ini, and you can change the default in Settingsuser.ini. When the script lists the detected parameters and paths and asks for your permission to go on, check the MS SQL version (check everything). If the version does not match the <Program> instance of SQL Server on the customer's machine, stop the script and edit Settingsuser.ini before you start it again. * To not overwrite user settings during deployment, this script is shipped with ProgramUpdaterSettingsusermaster.ini used for dev tests. You can check this e.g. for version updates. You are warned if script and ini versions (inside) don't fit. * The old version is detected from <WebsiteFolder>\bin&lt;FirstDll>.dll's file properties ('Product version'). * The new version is detected from D:\websites\deploy&lt;FirstMsi>.msi's file properties ('Tags'). * The backup folder is always <WebsiteFolder><OldVersion><DateTime>. * The script log folder is always D:\websites\deploy\UpdateLogs&lt;DateTime>. You will find some explicitly logged results, powershell script errors, and the UpgradeTool log in there. * DevExpress deployment folder is always D:\websites\deploy\DevExpress&lt;Version>. Ensure the DevExpress runtime files are copied to this folder before. * The script stops the IIS website for the duration of its run. .PARAMETER IniFileUser Default: <ThisScriptsPath>&lt;ThisScriptsNameWithoutExtension>Settingsuser.ini (almost always D:\Websites\Deploy\UpdaterScript\ProgramUpdaterSettingsuser.ini). .PARAMETER IniFileWebConfig Default: <ThisScriptsPath>&lt;ThisScriptsNameWithoutExtension>Settingsweb.config.ini (almost always D:\Websites\Deploy\UpdaterScript\ProgramUpdaterSettings_web.config.ini). .EXAMPLE PS C:\util> .\ProgramUpdaterAsAdmin.lnk # This script is shipped with ProgramUpdaterAsAdmin.lnk, you should prefer this. If you need to change the call e.g. to call another .ini, edit the .lnk. .NOTES Author: ps@<mydomain>.de Date: 2 June 2016 to 24 Jan 2019 Todo: $strNewVersion, zunaechst aus .msi-Props ausgelesen, aus dann entpacktem CC -> bin\ -> .dll neu auslesen, falls nicht zueinander passende Deploymentpakete .zip und .msi zusammenkopiert wurden [string]$strNewVersion = ((Get-ItemProperty ($strNewVerFolder + “bin\Company.Program.BusinessLogik.dll”)).VersionInfo).ProductVersion fuehrt aktuell zu Mosern ueber mangelnde Zugriffsrechte Todo: Optische Aufbereitung des Mailbodys oder Attachments ueber Webservice Version: 1.2.0.2 — check with TFS

#>

param ( [Parameter(Position=0)] # [string]$strWebsiteFolder = “D:\Websites\Company\Company.Program.Gui\” [string]$IniFileUser = $PSCommandPath -replace(”.ps1”,“Settingsuser.ini”), [Parameter(Position=1)] # [string]$strWebsiteFolder = “D:\Websites\Company\Company.Program.Gui\” [string]$IniFileWebConfig = $PSCommandPath -replace(”.ps1”,“Settingsweb.config.ini”)

)

PROCESS {

$Error.Clear()

if ($IniFileUser -in ($null, ””)) { [string]$IniFileUser = $PSCommandPath -replace(”.ps1”,“Settingsuser.ini”) } if ($IniFileUserWebConfig -in ($null, ””)) { [string]$IniFileWebConfig = $PSCommandPath -replace(”.ps1”,“Settingsweb.config.ini”) }

[string]$strWebConfigMsiContent = @” <?xml version=“1.0” encoding=“UTF-8”?> <configuration> <system.webServer> <directoryBrowse enabled=“true” showFlags=“Date, Time, Size, Extension, LongDate” /> </system.webServer> </configuration> ”@

[string]$strIniVersion = “1.5_20181019”

# if ($psise -ne $null) { $strWebsiteFolder = “Company” } # Edit the $strWebsiteFolder part of this line to run this script in ISE.

Add-Type -AssemblyName System.Windows.Forms Import-Module WebAdministration

#region Functions

Function Get-IniContent {
<# .Synopsis Gets the content of an INI file .Description Gets the content of an INI file and returns it as a hashtable .Notes Author : Oliver Lipkau <oliver@lipkau.net> Blog : http://oliver.lipkau.net/blog/ Source : https://github.com/lipkau/PsIni http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91 Version : 1.0 – 2010/03/12 – Initial release 1.1 – 2014/12/11 – Typo (Thx SLDR) Typo (Thx Dave Stiff) #Requires -Version 2.0 .Inputs System.String .Outputs System.Collections.Hashtable .Parameter FilePath Specifies the path to the input file. .Example $FileContent = Get-IniContent “C:\myinifile.ini” —————– Description Saves the content of the c:\myinifile.ini in a hashtable called $FileContent .Example $inifilepath | $FileContent = Get-IniContent —————– Description Gets the content of the ini file passed through the pipe into a hashtable called $FileContent .Example C:\PS>$FileContent = Get-IniContent “c:\settings.ini” C:\PS>$FileContent[“Section”][“Key”] —————– Description Returns the key “Key” of the section “Section” from the C:\settings.ini file .Link Out-IniFile #>

Param(
[ValidateNotNullOrEmpty()]
[ValidateScript({(Test-Path $) -and ((Get-Item $).Extension -eq ”.ini”)})]
[Parameter(ValueFromPipeline=$True,Mandatory=$True)]
[string]$FilePath
)

Write-Verbose ”$($MyInvocation.MyCommand.Name):: Function started”

Write-Verbose ”$($MyInvocation.MyCommand.Name):: Processing file: $Filepath”

$ini = @{}
switch -regex -file $FilePath
{
”^[(.+)]$” # Section {
$section = $matches[1]
$ini[$section] = @{}
$CommentCount = 0
}
”^(;.)$” # Comment {
if (!($section))
{
$section = “No-Section”
$ini[$section] = @{}
}
$value = $matches[1]
$CommentCount = $CommentCount + 1
$name = “Comment” + $CommentCount
$ini[$section][$name] = $value
}
”(.+?)\s
=\s(.)” # Key {
if (!($section))
{
$section = “No-Section”
$ini[$section] = @{}
}
$name,$value = $matches[1..2]
$ini[$section][$name] = $value
}
}

Write-Verbose ”$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath”
Return $ini

Write-Verbose ”$($MyInvocation.MyCommand.Name):: Function ended” } # Function Get-IniContent

function CopyWebConfigAttrValue($rootNew, $rootOld, $strSection, $strKeyName, $strLog) { [string]$strKeyValueRead = $rootOld.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' [string]$strKeyValueReadNew = $rootNew.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' if ( $strKeyValueReadNew -in ($null, ””) ) { $newItem = $rootNew.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[1]”).Clone() $newItem.SetAttribute(“key”,$strKeyName) $newItem.SetAttribute(“value”,$strKeyValueRead) $rootNew.SelectSingleNode(”//$strSection”).AppendChild($newItem) [string]$strMsg = “web.config: Setting '$strSection.$strKeyName' did not exist in webref.config (you should maintain webref.config) and has been automatically created by this script in target web.config, value has been set to '$strKeyValueRead', from former version's web.config. “ + (Get-Date -format “yyyyMMddHHmm”) Add-Type -AssemblyName PresentationFramework [System.Windows.MessageBox]::Show($strMsg,'<Program> Updater: New Setting','OK','Error') Add-Content -Value $strMsg -PassThru -Path $strLog } else { $rootNew.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' = $strKeyValueRead } [string]$strKeyValueWritten = $rootNew.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' switch ($strKeyName) { 'DbVerbindungString' { Add-Content -Value ($strKeyName + “: “ + $strKeyValueWritten + (Get-Date -format “yyyyMMddHHmm”)) -PassThru -Path $strLog } { $ -in 'SystemAccountPw', 'SmtpPw' } { $strKeyValueRead = ”(confidential)” $strKeyValueWritten = ”(confidential)” } } Add-Content -Value (“web.config: Setting '$strSection.$strKeyName': read '$strKeyValueRead', written '$strKeyValueWritten' “ + (Get-Date -format “yyyyMMddHHmm”)) -PassThru -Path $strLog } # function CopyWebConfigAttrValue

function SetWebConfigAttrValue($root, $strSection, $strKeyName, $strKeyValue, $strLog) { $strKeyValueRead = $root.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' if ( $strKeyValueRead -in ($null, ””) ) { $newItem = $root.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[1]”).Clone() $newItem.SetAttribute(“key”,$strKeyName) $newItem.SetAttribute(“value”,$strKeyValue) $rootNew.SelectSingleNode(”//$strSection”).AppendChild($newItem) [string]$strMsg = “web.config: Setting '$strSection.$strKeyName' did not exist in webref.config (you should maintain webref.config) and has been automatically created by this script in target web.config, value has been set to '$strKeyValueRead', from former version's web.config. “ + (Get-Date -format “yyyyMMdd_HHmm”) Add-Type -AssemblyName PresentationFramework [System.Windows.MessageBox]::Show($strMsg,'<Program> Updater: New Setting','OK','Error') Add-Content -Value $strMsg -PassThru -Path $strLog } else { $root.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' = $strKeyValue }

[string]$strKeyValueWritten = $root.SelectSingleNode(”//$strSection”).SelectSingleNode(“add[@key='$strKeyName']/@value”).'#text' switch ($strKeyName) { 'DbVerbindungString' { Add-Content -Value ($strKeyName + “: “ + $strKeyValueWritten + (Get-Date -format “yyyyMMddHHmm”)) -PassThru -Path $strLog } { $ -in 'SystemAccountPw', 'SmtpPw' } { $strKeyValueRead = ”(confidential)” $strKeyValueWritten = ”(confidential)” } } Add-Content -Value (“web.config: Setting '$strSection.$strKeyName' was set to '$strKeyValueWritten' “ + (Get-Date -format “yyyyMMdd_HHmm”)) -PassThru -Path $strLog } # function SetWebConfigAttrValue

# copy authorization keys – http://michlstechblog.info/blog/powershell-some-basic-xml-handling-with-powershell-and-net/ function CopyWebConfigAuthorizationNode($rootNew, $rootOld, $xmlConfigNew, $strLog) {

$rootNew.'system.web'.RemoveChild($rootNew.'system.web'.SelectSingleNode(“authorization”)) | Out-Null $newNode = $xmlConfigNew.ImportNode($rootOld.'system.web'.SelectSingleNode(“authorization”), $true) $rootNew.'system.web'.AppendChild($newNode) | Out-Null Add-Content -Value (“web.config: Copied 'authorization' section. “ + (Get-Date -format “yyyyMMdd_HHmm”)) -PassThru -Path $strLog

}

function AdaptWebConfig([string]$strNewVerFolder, [string]$strOldVerFolder, [string]$strNewVersion, [string]$strLog, $hshSettings) {

Rename-Item ($strNewVerFolder + “web_ref.config”) “web.config” [string]$strOldVerConfig = $strOldVerFolder + “web.config” [string]$strNewVerConfig = $strNewVerFolder + “web.config”

Add-Content -Value (“Old web.config: “ + $strOldVerConfig) -Path $strLog -PassThru Add-Content -Value (“New web.config: “ + $strNewVerConfig) -Path $strLog -PassThru

“Adapting $strNewVerConfig”

[xml]$xmlConfigOld = Get-Content $strOldVerConfig [xml]$xmlConfigNew = Get-Content $strNewVerConfig $rootOld = $xmlConfigOld.getDocumentElement() $rootNew = $xmlConfigNew.getDocumentElement()

[int]$intProgMainVersion = ($strNewVersion.Split(”.”))[0]

switch ($intProgMainVersion) { 2 { # NotebookVersion SetWebConfigAttrValue $rootNew “appSettingsNotebook” “NotebookVersion” $strNewVersion $strLog

# <authorization> CopyWebConfigAuthorizationNode $rootNew $rootOld $xmlConfigNew $strLog

# appSettings foreach ($key in $hshSettings[“web.config v2 appSettings”].Keys) { CopyWebConfigAttrValue $rootNew $rootOld “appSettings” $hshSettings[“web.config v2 appSettings”][$key] $strLog }

# appSettingsNotebook foreach ($key in $hshSettings[“web.config v2 appSettingsNotebook”].Keys) { CopyWebConfigAttrValue $rootNew $rootOld “appSettingsNotebook” $hshSettings[“web.config v2 appSettingsNotebook”][$key] $strLog }

} # switch 2

3 { # appSettings foreach ($key in $hshSettings[“web.config v3 appSettings”].Keys) { CopyWebConfigAttrValue $rootNew $rootOld “appSettings” $hshSettings[“web.config v3 appSettings”][$key] $strLog }

# appSettingsNotebook foreach ($key in $hshSettings[“web.config v3 appSettingsNotebook”].Keys) { CopyWebConfigAttrValue $rootNew $rootOld “appSettingsNotebook” $hshSettings[“web.config v3 appSettingsNotebook”][$key] $strLog }

} # switch 3 } # switch

$xmlConfigNew.Save($strNewVerConfig) | Out-Null

[string]$strNewVerConfigBak = $strNewVerFolder + “BakCfg\” if ((Test-Path $strNewVerConfigBak) -eq $false) { New-Item -Path (Split-Path -Path $strNewVerConfigBak -Parent) -Name (Split-Path -Path $strNewVerConfigBak -Leaf) -ItemType directory | Out-Null } $strNewVerConfigBak += (“web.config.” + (Get-Date -format “yyyyMMdd.HHmm”) + “h”) Copy-Item $strNewVerConfig $strNewVerConfigBak

} # function AdaptWebConfig

function MailResult ($strLogFolder, $hshSettings) { [string]$ThisScriptCopy = $PSCommandPath + ”.txt” Copy-Item $PSCommandPath $ThisScriptCopy -Force $dirLogs = Get-ChildItem ($strLogFolder + “*”)

[string[]]$astrAttachments = @() $astrAttachments += $ThisScriptCopy foreach ($strLogFile in $dirLogs) { $astrAttachments += $strLogFile.FullName }

[string]$strBody = “ProgramUpdater was started by “ + $env:USERDOMAIN + “\” + $env:USERNAME $strBody += ” on “ + [System.Net.Dns]::GetHostByName((hostname)).HostName

foreach ($strAttachment in $astrAttachments) { $strBody += nn ====================” $strBody += nn “ + $strAttachment $strBody += Get-Content $strAttachment }

[string[]]$astrMailRecipients = @() foreach ($key in $hshSettings[“Result mail”].Keys) { if ($key -like “To*”) { $astrMailRecipients += $hshSettings[“Result mail”][$key] } }

if ($hshSettings[“Result mail”][“Use”] -eq “1”) { Send-MailMessage -To $astrMailRecipients -From <span style="color:#19177c">$hshSettings</span>[<span style="color:#4070a0">"Result mail"</span>][<span style="color:#4070a0">"From"</span>] -Subject “ProgramUpdater” -Body <span style="color:#19177c">$strBody</span> -SmtpServer $hshSettings[“Result mail”][“SmtpServer”] -Attachments <span style="color:#19177c">$astrAttachments</span> -ErrorAction SilentlyContinue }

if ($hshSettings[“Result mail WS”][“Use”] -eq “1”) { $pw = ConvertTo-SecureString -String $hshSettings[“Result mail WS”][“WsUserPw”] -AsPlainText -Force $creds = New-Object System.Management.Automation.PSCredential $hshSettings[“Result mail WS”][“WsUserName”], $pw $wsSmtp = New-WebServiceProxy $hshSettings[“Result mail WS”][“WsUrl”] -Credential $creds foreach ($Addr in $astrMailRecipients) { $wsSmtp.SendeEmailMitStandardAbsender($Addr,””,””,“ProgramUpdater”,$strBody) } } } # function MailResult

Function BackslashTrailToFolder ($strFolder) { if ( ($strFolder.Substring($strFolder.Length – 1)) -ne “\” ) { $strFolder += “\” return $strFolder } }

Function FolderNameWoTrailingBackslash ($strFolder) { if ( ($strFolder.Substring($strFolder.Length – 1)) -eq “\” ) { $strFolder = $strFolder.Substring(0, $strFolder.Length – 1) return $strFolder } }

Function EnsureGuiName ($strFolder) { $strFolder = BackslashTrailToFolder($strFolder) [string]$strFolderGui = “Company.Program.Gui\” if ($strFolder.Length -lt $strFolderGui.Length) { [int]$intStartIx = 0 } else { [int]$intStartIx = $strFolder.Length$strFolderGui.Length }

if ($strFolder.Substring($intStartIx) -ne $strFolderGui) { $strFolder += $strFolderGui }

if ($strFolder.Substring(0, “D:\Websites\”.Length) -ne “D:\Websites\”) { $strFolder = “D:\Websites\” + $strFolder }

return $strFolder }

function Expand-File($file, $destination) { if ((Test-Path $destination) -eq $false) { New-Item -Path (Split-Path -Path $destination -Parent) -Name (Split-Path -Path $destination -Leaf) -ItemType directory | Out-Null }

$shell = new-object -com shell.application $zip = $shell.NameSpace($file) foreach($item in $zip.items()) { $shell.Namespace($destination).copyhere($item) } }

function TestRequirementFilesys($astrFileList, $strLog) { [bool]$blnResult = $true

For ($i=0; $i -lt ($astrFileList.Count); $i++) { Write-Host “Testing “ $astrFileList[$i] ” ... “ -NoNewline if ((Test-Path $astrFileList[$i]) -eq $false) { Add-Content -Value ($astrFileList[$i] + ” missing”) -Path $strLog Write-Host “`n” Throw ($astrFileList[$i] + ” missing”) $blnResult = $false } else { Write-Host “OK” } } if ($PSVersionTable.PSVersion.Major -lt 4) { Add-Content -Value “This script requires at least version 4 of PowerShell (in WMF 4, http://www.microsoft.com/en-us/download/details.aspx?id=40855)." -Path $strLog -PassThru $blnResult = $false } return $blnResult }

function GetDevExpressVersionFromProgramVersion($strNewVersion) { [int]$intProgMainVersion = ($strNewVersion.Split(”.”))[0]

switch ($intProgMainVersion)

{ 2 { return 9 } 3 { return 15 } } }

function GetFilePropTags([string]$strPath) { $objShell = New-Object -COMObject Shell.Application [string]$strFolder = Split-Path $strPath [string]$strFile = Split-Path $strPath -Leaf $objShellfolder = $objShell.Namespace($strFolder) $objShellfile = $objShellfolder.ParseName($strFile)

# To get a list of index numbers and their meaning, use this: # 0..287 | Foreach-Object { '{0} = {1}' -f $, $shellfolder.GetDetailsOf($null, $) }

[int]$intExtFilePropsTags = 18 $objShellfolder.GetDetailsOf($objShellfile, $intExtFilePropsTags) }

function GetMsiProdVer([string]$strPath) { $strTagsFromMsi = GetFilePropTags($strPath) [string[]]$astrFileTags = $strTagsFromMsi.Split(”,”) [int[]]$aintVno = ($astrFileTags[1]).split(”.”) [string]::Join(”.”, $aintVno) }

function GetDevExpressVersion([string]$strDevExpressFolder) { $dirDevExpress = Get-ChildItem $strDevExpressFolder *.dll ((Get-ItemProperty ($dirDevExpress[0].FullName)).VersionInfo).FileVersion }

#endregion

$hshProgramUpdaterSettings = Get-IniContent $IniFileUser $hshProgramUpdaterSettings += Get-IniContent $IniFileWebConfig

[string]$strDeploymentFolder = “D:\Websites\Deploy\” [string]$strLogFolder = $strDeploymentFolder + “UpdateLogs\” + (Get-Date -format “yyyyMMdd.HHmm”) + “h\” if ((Test-Path $strLogFolder) -eq $false) { New-Item -Path (Split-Path -Path $strLogFolder -Parent) -Name (Split-Path -Path $strLogFolder -Leaf) -ItemType directory | Out-Null } [string]$strLog = $strLogFolder + “ProgramUpdater + (Get-Date -format “yyyyMMddHHmm”) + “h.log”

if ( ( $hshProgramUpdaterSettings[“HeaderUser”][“Version”] -ne $strIniVersion ) -or ( $hshProgramUpdaterSettings[“HeaderWebConfig”][“Version”] -ne $strIniVersion ) ) { Add-Content -Value “Wrong version of at least 1 .ini.” -Path $strLog -PassThru Exit }

[string]$strVerMsSql = $hshProgramUpdaterSettings[“MssqlVersionen”][“DEFAULT”] [string]$strHostname = $env:COMPUTERNAME if ($null -ne $hshProgramUpdaterSettings[“MssqlVersionen”][$strHostname]) { $strVerMsSql = $hshProgramUpdaterSettings[“MssqlVersionen”][$strHostname] }

[string]$strWebsiteFolder = $hshProgramUpdaterSettings[“Website folder to Upgrade”][“Folder”] # $strWebsiteFolder.Trim(” “) while ( $strWebsiteFolder.EndsWith(” “) ) { $strWebsiteFolder = $strWebsiteFolder.Substring( 0, ($strWebsiteFolder.Length – 1) ) } $strWebsiteFolder = EnsureGuiName($strWebsiteFolder) [string]$strIISWebsiteName = (Get-Website | Where-Object -Property “PhysicalPath” -EQ -Value (FolderNameWoTrailingBackslash($strWebsiteFolder))).Name [string]$strIISAppPool = (Get-Item (“IIS:\Sites\” + $strIISWebsiteName) | Select-Object applicationPool).applicationPool [string]$strDeployZipFile = $strDeploymentFolder + “Company.Program.CC.Web.Gui.” + $strVerMsSql + ”.zip”

[string]$strOldVersion = ((Get-ItemProperty ($strWebsiteFolder + “bin\Company.Program.BusinessLogik.dll”)).VersionInfo).ProductVersion $msidir = Get-Item ($strDeploymentFolder + “Company.Program.Nb.Setup.” + $strVerMsSql + ”.*.msi”) [string]$strFirstMsiNewVer = $strDeploymentFolder + ($msidir[0].Name) [string]$strNewVersion = GetMsiProdVer($strFirstMsiNewVer) while ($null -eq $strNewVersion -or $strNewVersion -eq ””) { Write-Host “Die Zielversion konnte nicht aus $strFirstMsiNewVer ermittelt werden, bitte hier eingeben ('3.xx.x.xxxxx'):” -ForegroundColor Red $strNewVersion = Read-Host } [string]$strDevExpressFolder = $strDeploymentFolder + “DevExpress{0}\” -f (GetDevExpressVersionFromProgramVersion($strNewVersion)) [string]$strBackupFolder = FolderNameWoTrailingBackslash($strWebsiteFolder) $strBackupFolder += $strBackupFolder += $strOldVersion $strBackupFolder += $strBackupFolder += (Get-Date -format “yyyyMMdd.HHmm”) $strBackupFolder += “h\”

Write-Host -BackgroundColor Gray -ForegroundColor Blue -Object nnEffective Parameters for current run of this script” [string]$strThisScriptVersion = (Get-Help $PSCommandPath -Full).alertSet.alert[0] -split(“`n”) -match “Version: “ -replace(”}”,””)

$objParamTable = New-Object PSObject $objParamTable | Add-Member “Ini Files” ($IniFileUser + ”, “ + $IniFileWebConfig + ” — optional command line parameters”) $objParamTable | Add-Member “Website folder” $strWebsiteFolder $objParamTable | Add-Member “This script's version” $strThisScriptVersion $objParamTable | Add-Member “IIS website name” $strIISWebsiteName $objParamTable | Add-Member “IIS application pool name” $strIISAppPool $objParamTable | Add-Member “Old version” $strOldVersion $objParamTable | Add-Member “New version” $strNewVersion $objParamTable | Add-Member “MS SQL version” $strVerMsSql $objParamTable | Add-Member “Deploy folder” $strDeploymentFolder $objParamTable | Add-Member “Deploy zip file” $strDeployZipFile $objParamTable | Add-Member “DevExpress folder” $strDevExpressFolder $objParamTable | Add-Member “Backup folder” $strBackupFolder $objParamTable | Add-Member “Log folder” $strLogFolder $objParamTable | Format-List

[string[]]$astrFileList = ($strDeploymentFolder + .msi”) $astrFileList += ($strDeploymentFolder + “UpgradeTool-” + $strVerMsSql + ”.exe”) $astrFileList += $strDeployZipFile $astrFileList += ($strDevExpressFolder + “) $astrFileList += ($strWebsiteFolder + “*”) [bool]$blnResult = TestRequirementFilesys $astrFileList $strLog

if ($blnResult -eq $false) { Exit }

Write-Host -BackgroundColor Gray -ForegroundColor Blue -Object “If necessary, stop and run script again. Run '.\ProgramUpdater.ps1 –?' for more details.” Write-Host -BackgroundColor Gray -ForegroundColor Red -Object “If you don't understand what this script does read the <Program> installation guides.”

[string]$strTitle = “ProgramUpdater” [string]$strMessage = “Do you want to go on?” $yes = New-Object System.Management.Automation.Host.ChoiceDescription “&Yes”, <span style="color:#4070a0">"Go on."</span> <span style="color:#19177c">$no</span> = <span style="color:#06287e">New-Object</span> System.<span style="color:#06287e">Management</span>.<span style="color:#06287e">Automation</span>.<span style="color:#06287e">Host</span>.<span style="color:#06287e">ChoiceDescription</span> <span style="color:#4070a0">"&amp;No"</span>, “Exit the script.” $options = System.Management.Automation.Host.ChoiceDescription[] [int]$intResult = $host.ui.PromptForChoice($strTitle, $strMessage, $options, 1) switch ($intResult) { 1 {Exit} }

[string]$strFQDNThisMachine = [System.Net.Dns]::GetHostByName((hostname)).HostName Add-Content -Value (“Computer name (FQDN): “ + $strFQDNThisMachine) -Path $strLog Add-Content -Value (“Script starter: “ + $env:USERDOMAIN + “\” + $env:USERNAME) -Path $strLog Add-Content -Value (“Website folder: “ + $strWebsiteFolder) -Path $strLog Add-Content -Value (“Deploy folder: “ + $strDeploymentFolder) -Path $strLog Add-Content -Value (“Dev express folder: “ + $strDevExpressFolder) -Path $strLog Add-Content -Value (“Backup folder: “ + $strBackupFolder) -Path $strLog Add-Content -Value (“New version: “ + $strNewVersion) -Path $strLog Add-Content -Value (“IIS website name: “ + $strIISWebsiteName) -Path $strLog Add-Content -Value (“IIS application pool name: “ + $strIISAppPool) -Path $strLog Add-Content -Value (“MS SQL version: “ + $strVerMsSql) -Path $strLog Add-Content -Value (nn”) -Path $strLog

# Out-Null: Do not continue before this line has been completely executed Stop-Website -Name $strIISWebsiteName | Out-Null Stop-WebAppPool -Name $strIISAppPool | Out-Null Add-Content -Value (“Attempted to stop IIS website “ + $strIISWebsiteName + + (Get-Date -format “yyyyMMddHHmm”)) -Path $strLog -PassThru Add-Content -Value (“Attempted to stop IIS application pool “ + $strIISAppPool + + (Get-Date -format “yyyyMMddHHmm”)) -Path $strLog -PassThru

Add-Content -Value (“Attempting to ensure apppool” + $strIISAppPool + ” and website “ + $strIISWebsiteName + ” are stopped. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Add-Content -Value (“Apppool “ + $strIISAppPool + ” status: “ + (Get-WebAppPoolState $strIISAppPool).Value + ”. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru Add-Content -Value (“Website “ + $strIISWebsiteName + ” status: “ + (Get-WebsiteState $strIISWebsiteName).Value + ”. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

“Waiting” | Out-Null

[int]$intSec = 0 while ( ( (Get-WebAppPoolState $strIISAppPool).Value -ne “Stopped” ) -and ( (Get-WebsiteState $strIISWebsiteName).Value -ne “Stopped” ) ) { Start-Sleep 2 $intSec += 2 Add-Content -Value (“Waited “ + $intSec + ” of 60 seconds. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru if ($intSec -eq 60) { Add-Content -Value (“apppool and website apparently still running, exiting. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru Exit } }

[string]$strNewVerFolder = FolderNameWoTrailingBackslash($strWebsiteFolder) $strNewVerFolder += “_New\”

Expand-File -file $strDeployZipFile -destination $strNewVerFolder | Out-Null Add-Content -Value (“Unzipped “ + $strDeployZipFile + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Remove-Item -Path ($strWebsiteFolder + “bin\DevExpress) -Force -Recurse | Out-Null Add-Content -Value (“Deleted “ + $strWebsiteFolder + “bin\DevExpress “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

if ((Test-Path $strNewVerFolder) -eq $false) { New-Item -Path (Split-Path -Path $strNewVerFolder -Parent) -Name (Split-Path -Path $strNewVerFolder -Leaf) -ItemType directory | Out-Null } if ((Test-Path $strBackupFolder) -eq $false) { New-Item -Path (Split-Path -Path $strBackupFolder -Parent) -Name (Split-Path -Path $strBackupFolder -Leaf) -ItemType directory | Out-Null }

Copy-Item -Path ($strDevExpressFolder + ) -Destination ($strNewVerFolder + “bin”) -Force | Out-Null Add-Content -Value (“Copied “ + $strDevExpressFolder + “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

try { Move-Item -Path ($strWebsiteFolder + ) -Destination $strBackupFolder -Exclude “Log”,“Setup”,“Temp”,“logo” -Force -ErrorAction SilentlyContinue | Out-Null
} catch { } Add-Content -Value (“Moved “ + $strWebsiteFolder +
except Log\, Setup\, Temp\, logo\ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Copy-Item -Path ($strWebsiteFolder + “*”) -Destination $strBackupFolder -Force -Recurse | Out-Null Add-Content -Value (“Copied “ + $strWebsiteFolder + “\Log\, ...\Setup\, ...\Temp\, ...\logo\ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

AdaptWebConfig $strNewVerFolder $strBackupFolder $strNewVersion $strLog $hshProgramUpdaterSettings

Remove-Item -Path ($strWebsiteFolder + “Setup\Company.Program.Nb.Setup..msi”) -Force | Out-Null Add-Content -Value (“Deleted “ + $strWebsiteFolder + “Setup\Company.Program.Nb.Setup..msi “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

[string]$strMsiCopyPath = $strWebsiteFolder + “Setup\msi” if ((Test-Path $strMsiCopyPath) -eq $false) { New-Item -Path (Split-Path -Path $strMsiCopyPath -Parent) -Name (Split-Path -Path $strMsiCopyPath -Leaf) -ItemType directory | Out-Null } else { Remove-Item -Path ($strMsiCopyPath + “\Company.Program.Nb.Setup.*.msi”) -Force | Out-Null }

[string]$strWebConfigMsiPath = $strMsiCopyPath + “\web.config” if ((Test-Path $strWebConfigMsiPath) -eq $false) { $strWebConfigMsiContent | Out-File $strWebConfigMsiPath -Force -Encoding utf8 [string]$strWebConfigMsiAttr = (Get-Item -Force $strWebConfigMsiPath).Attributes -join(”,”) if ($strWebConfigMsiAttr -notmatch(“Hidden”)) { (Get-Item -Force $strWebConfigMsiPath).Attributes = $strWebConfigMsiAttr + ”,Hidden” } Add-Content -Value (“Created “ + $strWebConfigMsiPath + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru }

Add-Content -Value (“Deleted “ + $strWebsiteFolder + “Setup\msi\Company.Program.Nb.Setup.*.msi “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Remove-Item -Path ($strWebsiteFolder + “Setup\UpgradeLog.txt”) -Force | Out-Null Add-Content -Value (“Deleted “ + $strWebsiteFolder + “Setup\UpgradeLog.txt “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Remove-Item -Path ($strWebsiteFolder + “Setup\UpgradeTool-” + $strVerMsSql + ”.exe”) -Force | Out-Null Add-Content -Value (“Deleted “ + $strWebsiteFolder + “Setup\UpgradeTool-” + $strVerMsSql + ”.exe “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Remove-Item -Path ($strWebsiteFolder + “Log*.) -Force | Out-Null Add-Content -Value (“Deleted “ + $strWebsiteFolder + “Log*. “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

while ((Get-ChildItem $strNewVerFolder).Length -gt 0) { Move-Item -Path ($strNewVerFolder + ) -Destination $strWebsiteFolder -Force -ErrorAction SilentlyContinue | Out-Null } Add-Content -Value (“Moved “ + $strNewVerFolder + “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Remove-Item -Path ($strNewVerFolder) -Force -Recurse | Out-Null Add-Content -Value (“Deleted “ + $strNewVerFolder + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Copy-Item -Path ($strDeploymentFolder + “Company.Program.Nb.Setup.” + $strVerMsSql + .msi”) -Destination ($strWebsiteFolder + “Setup”) -Force | Out-Null Add-Content -Value (“Copied “ + $strDeploymentFolder + “Company.Program.Nb.Setup.” + $strVerMsSql + .msi to Setup\” + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Copy-Item -Path ($strDeploymentFolder + “Company.Program.Nb.Setup.” + $strVerMsSql + .msi”) -Destination ($strWebsiteFolder + “Setup\msi”) -Force | Out-Null Add-Content -Value (“Copied “ + $strDeploymentFolder + “Company.Program.Nb.Setup.” + $strVerMsSql + .msi to Setup\msi\” + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

Copy-Item -Path ($strDeploymentFolder + “UpgradeTool-” + $strVerMsSql + ”.exe”) -Destination ($strWebsiteFolder + “Setup”) -Force | Out-Null Add-Content -Value (“Copied “ + $strDeploymentFolder + “UpgradeTool-” + $strVerMsSql + ”.exe” + ” “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

[string]$strTitle = “ProgramUpdater” [string]$strMessage = “Do you want the script to copy the path to the web.config to clipboard, for UpgradeTool? “ $strMessage += “This will overwrite current contents of clipboard.” $yes = New-Object System.Management.Automation.Host.ChoiceDescription “&Yes”, <span style="color:#4070a0">"Copy the path, overwrite the clipboard contents."</span> <span style="color:#19177c">$no</span> = <span style="color:#06287e">New-Object</span> System.<span style="color:#06287e">Management</span>.<span style="color:#06287e">Automation</span>.<span style="color:#06287e">Host</span>.<span style="color:#06287e">ChoiceDescription</span> <span style="color:#4070a0">"&amp;No"</span>, “Do not copy anything, just start the UpgradeTool.” $options = System.Management.Automation.Host.ChoiceDescription[] [int]$intResult = $host.ui.PromptForChoice($strTitle, $strMessage, $options, 0) switch ($intResult) { 0 { [Windows.Forms.Clipboard]::SetText($strWebsiteFolder + “web.config”) } }

$UpgradeToolPid = (Start-Process -FilePath ($strWebsiteFolder + “Setup\UpgradeTool-” + $strVerMsSql + ”.exe”) -Wait -PassThru).Id try { Wait-Process -Id $UpgradeToolPid -ErrorAction SilentlyContinue } catch {
}

Copy-Item -Path ($strWebsiteFolder + “Setup\UpgradeLog.txt”) -Destination ($strLogFolder) -Force | Out-Null

Start-Website -Name $strIISWebsiteName Start-WebAppPool -Name $strIISAppPool | Out-Null # “Started IIS website ” + $strIISWebsiteName + “ ” + (Get-Date -format “yyyyMMddHHmm”) -> PassThru # “Started IIS application pool ” + $strIISAppPool + “ ” + (Get-Date -format “yyyyMMddHHmm”) Add-Content -Value (“Attempting to start IIS website “ + $strIISWebsiteName + ” “ + (Get-Date -format “yyyyMMddHHmm”)) -Path $strLog -PassThru | Out-Null Add-Content -Value (“Attempting to start IIS application pool “ + $strIISAppPool + ” “ + (Get-Date -format “yyyyMMddHHmm”)) -Path $strLog -PassThru | Out-Null

“Waiting” | Out-Null

[int]$intSec = 0 while ( ( (Get-WebAppPoolState $strIISAppPool).Value -ne “Started” ) -and ( (Get-WebsiteState $strIISWebsiteName).Value -ne “Started” ) ) { Start-Sleep 2 $intSec += 2 Add-Content -Value (“Waited “ + $intSec + ” of 60 seconds. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru if ($intSec -eq 60) { Add-Content -Value (“apppool and website not running, upgrader failed, result mail might fail. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru break } }

Add-Content -Value (“Apppool “ + $strIISAppPool + ” status: “ + (Get-WebAppPoolState $strIISAppPool).Value + ”. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru Add-Content -Value (“Website “ + $strIISWebsiteName + ” status: “ + (Get-WebsiteState $strIISWebsiteName).Value + ”. _ “ + (Get-Date -format “yyyyMMdd_HHmm”)) -Path $strLog -PassThru

$Error | Select-Object * | Out-File ($strLogFolder + “ProgramUpdater.errors” + (Get-Date -format “yyyyMMdd.HHmm”) + “h.log”)

MailResult $strLogFolder $hshProgramUpdaterSettings

}

To produce a widely accepted signature that validates as a PAdES signature, you need to sign your PDF with an SSL signature of X.509 standard, that the issuing Certificate Authority (CA) has flagged to be intended for code / object signing. In this contribution I stress on the act of signing PDF documents, and just say: Inform yourself e.g. on the website of the association cacert.org how to obtain a code and object signing SSL certificate, and how to handle, configure and use certificates in your system(s), browser(s) and applications. As long as you don't have an SSL certificate, you cannot use this kind of official signature method, but can sign it with GPG.

The process itself is very easy if you use LibreOffice and Firefox. You can export to PDF every LibreOffice document and in the export dialogue select your personal digital signature certificate. Or you sign an existing PDF by (menu) File / Digital Signatures / Sign Existing PDF. But before doing it for the first time, you might need to tell LibreOffice where your Firefox profile with the Firefox certificate store is located, and maintain a list of timestamp servers' URLs that work for you.

When I first selected (menu) File / Export as / Export as PDF / (tab) Digital signatures / (button) Select..., I saw ... nothing.

DigitalSignature tab in »Export As PDF« dialogue.

It took me hours to find the reason: LibreOffice on Linux searches for signing certs in Firefox' or Thunderbird's certificate store, I had migrated my Firefox profile from Windows, and instead of throwing the profile contents into the default profile folder I had used another folder and edited Firefox' configuration. So I manually gave LibreOffice the differing path in Tools / Options / Security / Certificate Path, and could select my certificate. I could sign my PDF, open it in LibreOffice Draw, and it validated as a correct Adobe signature.

NSS path in LibreOffice Options.

Since timestamping signatures is a good idea, I searched and found a list of free timestamp servers. I inserted some URLs into the list in (menu) Tools / Options / Security / (button) TSAs, tested them, and when I selected one of the entries to timestamp my signature in the (menu) File / Export As / Export As PDF / (tab) Digital Signatures / (dropdown) Time Stamp Authority, my signature validated as a PAdES signature in LibreOfficeDraw.

 $ pdfsig path.pdf -nssdir (Firefox profile path) -dump

revealed that the timestamp server URL is part of the signature. (pdfsig is part of the poppler-utils, Linux Mint Software Center installed them for me.)

In Markdown and in several CMS sufficient code beautifiers work out of the box. But what if you dislike the color pattern or need more control? Manually apply HTML styles to every key word? And if you need the styles inline because your CMS ignores <styles>, but let's you insert inline format commands? You would not do that.

You need pandoc, and if you need inline styles, you need premailer. If you are content with the first results, you are done in a few seconds. If you want to author your own style patterns, that will take you some hours the first time. But never as long as a manual edit would take.

This has been tested on Linux Mint Ulyana. Since Python and pandoc exist for almost every real system around (not for the little pocket play machines), you should be able to do this on every serious machine.

Preparation (once):

Download pandoc. Play with my sample command and the several styles you can use. Create your own styles, if you want.

Only if you need inline styles: Ensure Python and PIP in your System. Install premailer.

Beautify a peace of code works this way:

1. pandoc works easiest with Markdown input. Embrace the code part by at least three ~~~ and define the language (cf. list of languages pandoc supports and highlights, in its man pages / command line help or on its website.)

  ~~~~~~ {.powershell}
  [...code...]
  ~~~~~~

Tip: To show markdown code on a markdown site, nest two ~ row pairs, use another count of ~ for start and end row of the inner pair than for the other.

2. Have pandoc convert your .md to .html. Fenced code blocks (those embraced by ~~~) are automatically syntax highlighted. With -s, pandoc writes a standalone html that you can open in your browser and immediately check the result. If you don't like the color pattern, set another highlighter on the command line. If you want, you can create your own highlighter for repeated use.

$ pandoc "/path/file.ps1.md" -s -o "/path/file.ps1.html" --metadata pagetitle="file.ps1"

If you have full control over your website's code, you can use the <style>s and the lines of code directly in your html. If your target is an edit control in a CMS or a mail body editor you will need the next step.

3. Have premailer convert <style>s into inline styles.

$ python3 -m premailer -f "/path/file.ps1.html" > "/path/file.ps1.inline.html"