From 01178b28e43b4784e744882ce13766ca759ebcf7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 7 Feb 2019 22:38:20 +0000 Subject: [PATCH 001/175] Update notifications and failure recovery > Notify upon update with result, new version, and old version. > Rename old version and delete if successful, or put it back in place if unsuccessful (instead of deleting the old version before updating). Failure detection is very basic at this point and should be improved. --- UpdateVeeamDiscordNotification.ps1 | 71 +++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 127a865..67c955d 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -2,6 +2,54 @@ Param ( [string]$LatestVersion ) +# Notification script block +$notification = { + # Create embed and fields array + [System.Collections.ArrayList]$embedarray = @() + [System.Collections.ArrayList]$fieldarray = @() + # Thumbnail object + $thumbobject = [PSCustomObject]@{ + url = $currentconfig.thumbnail + } + # Field objects + $resultfield = [PSCustomObject]@{ + name = 'Update Result' + value = $result + inline = 'false' + } + $newversionfield = [PSCustomObject]@{ + name = 'New version' + value = $newversion + inline = 'false' + } + $oldversionfield = [PSCustomObject]@{ + name = 'Old version' + value = $oldversion + inline = 'false' + } + # Add field objects to the field array + $fieldarray.Add($oldversionfield) | Out-Null + $fieldarray.Add($newversionfield) | Out-Null + $fieldarray.Add($resultfield) | Out-Null + # Embed object including field and thumbnail vars from above + $embedobject = [PSCustomObject]@{ + title = $JobName + color = '1267393' + thumbnail = $thumbobject + fields = $fieldarray + } + # Add embed object to the array created above + $embedarray.Add($embedobject) | Out-Null + # Build payload + $payload = [PSCustomObject]@{ + embeds = $embedarray + } + # Send iiit + Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' +} + +# Get currently downloaded version +$oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" # Import functions Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Start logging @@ -23,8 +71,8 @@ while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandlin Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip # Expand downloaded ZIP Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot -# Remove old version except log files. -Get-ChildItem $PSScriptRoot\VeeamDiscordNotifications\ -Include *.* -Exclude '*.log*', -File -Recurse | Remove-Item -Force +# Rename old version to make room for the new version +Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old # Rename extracted update Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications # Pull configuration from new conf file @@ -48,8 +96,27 @@ if ($currentConfig.auto_update -ne $newConfig.auto_update) { } ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" +# Get newly downloaded version +$newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" + +# Send notification +If ($newversion -eq $LatestVersion) { + $result = 'Success!' + Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force + Write-Output 'Successfully updated.' + Invoke-Command -ScriptBlock $notification +} +Else { + $result = 'Failure!' + Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + Write-Output 'Update failed, reverted to previous version.' + Invoke-Command -ScriptBlock $notification +} + # Clean up. Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force + # Stop logging Stop-Logging "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" From 90efeb9efe505f774bc64069955934e7fa5abbd5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 8 Feb 2019 11:59:32 +0000 Subject: [PATCH 002/175] Fixed logging and download > Logging was breaking everything. It was logging to a folder that was later to be renamed. This caused the rename to fail as PS was locking the log file. > Unable to download the file as PS uses old security protocols by default. --- UpdateVeeamDiscordNotification.ps1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 67c955d..f86a54d 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -53,7 +53,7 @@ $oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\ver # Import functions Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Start logging -Start-Logging "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" +Start-Logging "$PSScriptRoot\update.log" # Pull current config to variable $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json @@ -68,6 +68,7 @@ while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandlin } } # Pull latest version of script from GitHub +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip # Expand downloaded ZIP Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot @@ -119,4 +120,6 @@ Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force # Stop logging -Stop-Logging "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" +Stop-Logging "$PSScriptRoot\update.log" +# Copy item +Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" From b3f8523b367c91975eb26f9aeb8601baa9756a00 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 8 Feb 2019 12:17:59 +0000 Subject: [PATCH 003/175] Fix notifications --- UpdateVeeamDiscordNotification.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index f86a54d..67565bb 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -33,7 +33,7 @@ $notification = { $fieldarray.Add($resultfield) | Out-Null # Embed object including field and thumbnail vars from above $embedobject = [PSCustomObject]@{ - title = $JobName + title = 'Update' color = '1267393' thumbnail = $thumbobject fields = $fieldarray From c120a792e434ce55d54035d91d1c2b7d359c64c3 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 15:51:04 +0000 Subject: [PATCH 004/175] Frameworks for error handling. >Set error action preference to stop, changing default non-terminating error action from continue to stop, making it a terminating error. >Moved important commands to try/catch blocks. >Added ` -ErrorAction Continue` to unimportant commands so that errors do not terminate the script. --- UpdateVeeamDiscordNotification.ps1 | 123 +++++++++++++++++++++++------ 1 file changed, 99 insertions(+), 24 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 67565bb..77b3f43 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -2,6 +2,10 @@ Param ( [string]$LatestVersion ) + +# Set error action preference. +$ErrorActionPreference = 'Stop' + # Notification script block $notification = { # Create embed and fields array @@ -49,14 +53,29 @@ $notification = { } # Get currently downloaded version -$oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" +Try { + Write-Output 'Getting currently downloaded version of the script.' + $oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} + # Import functions Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Start logging Start-Logging "$PSScriptRoot\update.log" # Pull current config to variable -$currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json +Try { + Write-Output 'Pull current config to variable.' + $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { @@ -67,38 +86,94 @@ while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandlin exit } } + # Pull latest version of script from GitHub -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip +Try { + Write-Output 'Pull latest version of script from GitHub.' + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} + # Expand downloaded ZIP -Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot +Try { + Write-Output 'Expand downloaded ZIP.' + Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} + # Rename old version to make room for the new version -Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old +Try { + Write-Output 'Rename old version to make room for the new version.' + Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} + # Rename extracted update -Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications +Try { + Write-Output 'Rename extracted update.' + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} + # Pull configuration from new conf file -$newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json -# Unblock script files -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotification.ps1 -# Set conf.json as it was before -$newConfig.webhook = $currentConfig.webhook -$newConfig.userid = $currentConfig.userid -if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { - $newConfig.mention_on_fail = $currentConfig.mention_on_fail +Try { + Write-Output 'Rename extracted update.' + $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } -if ($currentConfig.debug_log -ne $newConfig.debug_log) { - $newConfig.debug_log = $currentConfig.debug_log +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" } -if ($currentConfig.auto_update -ne $newConfig.auto_update) { - $newConfig.auto_update = $currentConfig.auto_update + +# Unblock script files +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotification.ps1 -ErrorAction Continue + +# Populate conf.json with previous configuration +Try { + Write-Output 'Populate conf.json with previous configuration.' + $newConfig.webhook = $currentConfig.webhook + $newConfig.userid = $currentConfig.userid + if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { + $newConfig.mention_on_fail = $currentConfig.mention_on_fail + } + if ($currentConfig.debug_log -ne $newConfig.debug_log) { + $newConfig.debug_log = $currentConfig.debug_log + } + if ($currentConfig.auto_update -ne $newConfig.auto_update) { + $newConfig.auto_update = $currentConfig.auto_update + } + ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" } -ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" # Get newly downloaded version -$newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" +Try { + Write-Output 'Get newly downloaded version.' + $newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" +} +Catch { + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorvar" +} # Send notification If ($newversion -eq $LatestVersion) { From b3b7ec0efc08fa96f1f5a6c2a55c7b6e0bb24022 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 15:57:34 +0000 Subject: [PATCH 005/175] Notification function instead of script block Moved the notification script block to a function. --- UpdateVeeamDiscordNotification.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 77b3f43..e49f6d8 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -6,8 +6,8 @@ Param ( # Set error action preference. $ErrorActionPreference = 'Stop' -# Notification script block -$notification = { +# Notification function +function notification { # Create embed and fields array [System.Collections.ArrayList]$embedarray = @() [System.Collections.ArrayList]$fieldarray = @() @@ -196,5 +196,5 @@ Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force # Stop logging Stop-Logging "$PSScriptRoot\update.log" -# Copy item +# Move log file Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" From be2c99a8438a7ac17bb6f073a5e4fabcf2959568 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:00:46 +0000 Subject: [PATCH 006/175] Add success and fail functions Add a function for success and another for fail, to be triggered upon job completion or terminating error. These will need to be improved before release, for instance currently if the job fails in early stages, it will nuke the original VeeamDiscordNotifications install. Obviously a bit of an issue. --- UpdateVeeamDiscordNotification.ps1 | 45 ++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index e49f6d8..24ab0f1 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -51,6 +51,31 @@ function notification { # Send iiit Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' } +# Success function +function success { + $result = 'Success!' + Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force + Write-Output 'Successfully updated.' + Invoke-Expression notification + # Stop logging + Stop-Logging "$PSScriptRoot\update.log" + # Move log file + Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" + Exit-PSSession +} +# Failure function +function fail { + $result = 'Failure!' + Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + Write-Output 'Update failed, reverted to previous version.' + Invoke-Expression notification + # Stop logging + Stop-Logging "$PSScriptRoot\update.log" + # Move log file + Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" + Exit-PSSession +} # Get currently downloaded version Try { @@ -60,6 +85,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Import functions @@ -75,6 +101,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. @@ -96,6 +123,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Expand downloaded ZIP @@ -106,6 +134,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Rename old version to make room for the new version @@ -116,6 +145,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Rename extracted update @@ -126,6 +156,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Pull configuration from new conf file @@ -136,6 +167,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Unblock script files @@ -163,6 +195,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Get newly downloaded version @@ -173,21 +206,15 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" + Invoke-Expression fail } # Send notification If ($newversion -eq $LatestVersion) { - $result = 'Success!' - Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force - Write-Output 'Successfully updated.' - Invoke-Command -ScriptBlock $notification + Invoke-Expression success } Else { - $result = 'Failure!' - Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - Write-Output 'Update failed, reverted to previous version.' - Invoke-Command -ScriptBlock $notification + Invoke-Expression fail } # Clean up. From 0987b54bc7b4220925d899828dd7aa8862951238 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:02:39 +0000 Subject: [PATCH 007/175] Send error with notification Need to consider the alternative of sending as a separate notification as this could be quite long. Another alternative is to add it to the embed/message body instead of a field, this could be best. --- UpdateVeeamDiscordNotification.ps1 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 24ab0f1..5b7e058 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -35,6 +35,15 @@ function notification { $fieldarray.Add($oldversionfield) | Out-Null $fieldarray.Add($newversionfield) | Out-Null $fieldarray.Add($resultfield) | Out-Null + # Send error if exist + If ($errorvar -ne $null) { + $errorfield = [PSCustomObject]@{ + name = 'Update Error' + value = $errorvar + inline = 'false' + } + $fieldarray.Add($errorfield) | Out-Null + } # Embed object including field and thumbnail vars from above $embedobject = [PSCustomObject]@{ title = 'Update' From b4301e1c54bd256615ef352567b218a719878cb9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:07:26 +0000 Subject: [PATCH 008/175] Corrected an incorrect output. --- UpdateVeeamDiscordNotification.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 5b7e058..5e06a6c 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -170,7 +170,7 @@ Catch { # Pull configuration from new conf file Try { - Write-Output 'Rename extracted update.' + Write-Output 'Pull configuration from new conf file.' $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { From 4a9916cfb9210cc12cdd223e00b29a12d67dff76 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:52:32 +0000 Subject: [PATCH 009/175] Add output for `unblock-file` stage --- UpdateVeeamDiscordNotification.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 5e06a6c..ac6a22b 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -180,6 +180,7 @@ Catch { } # Unblock script files +Write-Output 'Unblock script files.' Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -ErrorAction Continue Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -ErrorAction Continue Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -ErrorAction Continue From 3f5a969f6d7453358064af8de387a5ef98928e99 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 21 Feb 2019 16:59:20 +0000 Subject: [PATCH 010/175] Removed redundant parts of functions These were unnecessary and causing issues in some scenarios --- UpdateVeeamDiscordNotification.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index ac6a22b..31b436f 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -66,11 +66,6 @@ function success { Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force Write-Output 'Successfully updated.' Invoke-Expression notification - # Stop logging - Stop-Logging "$PSScriptRoot\update.log" - # Move log file - Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" - Exit-PSSession } # Failure function function fail { @@ -79,11 +74,6 @@ function fail { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications Write-Output 'Update failed, reverted to previous version.' Invoke-Expression notification - # Stop logging - Stop-Logging "$PSScriptRoot\update.log" - # Move log file - Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" - Exit-PSSession } # Get currently downloaded version From 8e29fa3184304db771688a82e7a2dac7397c6041 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 22 Feb 2019 09:45:21 +0000 Subject: [PATCH 011/175] Various fixes and better logging. >Log from beginning of script instead of after a few things have already happened. >More verbose output to console & log. >Failure to send a notification will not kill the script. --- UpdateVeeamDiscordNotification.ps1 | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 31b436f..1f40b81 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -3,11 +3,18 @@ Param ( [string]$LatestVersion ) +# Import functions +Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" +# Start logging +Start-Logging "$PSScriptRoot\update.log" + # Set error action preference. +Write-Output 'Set error action preference.' $ErrorActionPreference = 'Stop' # Notification function function notification { + Write-Output 'Building notification.' # Create embed and fields array [System.Collections.ArrayList]$embedarray = @() [System.Collections.ArrayList]$fieldarray = @() @@ -57,22 +64,23 @@ function notification { $payload = [PSCustomObject]@{ embeds = $embedarray } + Write-Output 'Sending notification.' # Send iiit - Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' -ErrorAction Continue } # Success function function success { + Write-Output 'Successfully updated.' $result = 'Success!' Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force - Write-Output 'Successfully updated.' Invoke-Expression notification } # Failure function function fail { + Write-Output 'Update failed, reverting to previous version.' $result = 'Failure!' Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - Write-Output 'Update failed, reverted to previous version.' Invoke-Expression notification } @@ -87,11 +95,6 @@ Catch { Invoke-Expression fail } -# Import functions -Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" -# Start logging -Start-Logging "$PSScriptRoot\update.log" - # Pull current config to variable Try { Write-Output 'Pull current config to variable.' @@ -218,10 +221,15 @@ Else { } # Clean up. +Write-Output 'Remove downloaded ZIP.' Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" +Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force # Stop logging +Write-Output 'Stop logging.' Stop-Logging "$PSScriptRoot\update.log" # Move log file +Write-Output 'Move log file.' Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" +Write-Output 'Exiting.' From 826af73de133e132f8853e4deee5600e7f0474f0 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 28 Feb 2019 13:57:53 +0000 Subject: [PATCH 012/175] Better failure handling in the update script --- UpdateVeeamDiscordNotification.ps1 | 104 ++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 1f40b81..1baa6fe 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -13,7 +13,7 @@ Write-Output 'Set error action preference.' $ErrorActionPreference = 'Stop' # Notification function -function notification { +function Update-Notification { Write-Output 'Building notification.' # Create embed and fields array [System.Collections.ArrayList]$embedarray = @() @@ -69,21 +69,66 @@ function notification { Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' -ErrorAction Continue } # Success function -function success { +function Update-Success { Write-Output 'Successfully updated.' $result = 'Success!' Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force - Invoke-Expression notification + Invoke-Expression Update-Notification } # Failure function -function fail { - Write-Output 'Update failed, reverting to previous version.' +function Update-Fail { $result = 'Failure!' + Switch ($fail) { + download { + Write-Output 'Failed to download update.' + } + unzip { + Write-Output 'Failed to unzip update. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + } + rename_old { + Write-Output 'Failed to rename old version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + } + rename_new { + Write-Output 'Failed to rename new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + after_rename_new { + Write-Output 'Failed after renaming new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + } + Write-Output 'Update failed. Previous version restored.' Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - Invoke-Expression notification + Invoke-Expression Update-Notification +} +# End of script function +function End-Script { + # Clean up. + Write-Output 'Remove downloaded ZIP if it''s still there.' + If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { + Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" + } + Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' + Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force + + # Stop logging + Write-Output 'Stop logging.' + Stop-Logging "$PSScriptRoot\update.log" + # Move log file + Write-Output 'Move log file.' + Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" + Write-Output 'Exiting.' } + # Get currently downloaded version Try { Write-Output 'Getting currently downloaded version of the script.' @@ -92,7 +137,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + Invoke-Expression Update-Fail } # Pull current config to variable @@ -103,7 +148,7 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + Invoke-Expression Update-Fail } # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. @@ -112,8 +157,8 @@ while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandlin Start-Sleep -Seconds 1 If ($timer -eq '60') { Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' - exit } + Invoke-Expression Update-Fail } # Pull latest version of script from GitHub @@ -125,7 +170,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'download' + Invoke-Expression Update-Fail } # Expand downloaded ZIP @@ -136,7 +182,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'unzip' + Invoke-Expression Update-Fail } # Rename old version to make room for the new version @@ -147,7 +194,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'rename_old' + Invoke-Expression Update-Fail } # Rename extracted update @@ -158,7 +206,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'rename_new' + Invoke-Expression Update-Fail } # Pull configuration from new conf file @@ -169,7 +218,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Unblock script files @@ -198,7 +248,8 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Get newly downloaded version @@ -209,27 +260,14 @@ Try { Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorvar" - Invoke-Expression fail + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Send notification If ($newversion -eq $LatestVersion) { - Invoke-Expression success + Invoke-Expression Update-Success } Else { - Invoke-Expression fail -} - -# Clean up. -Write-Output 'Remove downloaded ZIP.' -Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" -Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' -Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force - -# Stop logging -Write-Output 'Stop logging.' -Stop-Logging "$PSScriptRoot\update.log" -# Move log file -Write-Output 'Move log file.' -Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" -Write-Output 'Exiting.' + Invoke-Expression Update-Fail +} From c6934bc957bd3d12c9e241a59b27ad6139b67d01 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 28 Feb 2019 21:37:57 +0000 Subject: [PATCH 013/175] Fixed success/fail functions not calling end script function --- UpdateVeeamDiscordNotification.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 1baa6fe..e0092fe 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -74,6 +74,7 @@ function Update-Success { $result = 'Success!' Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force Invoke-Expression Update-Notification + Invoke-Expression End-Script } # Failure function function Update-Fail { @@ -108,6 +109,7 @@ function Update-Fail { Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications Invoke-Expression Update-Notification + Invoke-Expression End-Script } # End of script function function End-Script { From d11713f43e84a128e4b350b5dfafc34cfd072d33 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 21:28:18 +0100 Subject: [PATCH 014/175] Version detection fix and formatting. - Version was not being properly detected in some cases. Now reading file raw. - Added line breaks where I'd missed some. --- DiscordVeeamAlertSender.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index ec2b7da..4af6ddd 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -17,13 +17,16 @@ if($config.debug_log) { # Determine if an update is required. ## Get currently downloaded version of this project. -$currentversion = Get-Content "$PSScriptRoot\resources\version.txt" +$currentversion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw + ## Get latest release from GitHub and use that to determine the latest version. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $latestrelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing + ## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"} so we need to extract tag_name. $latestreleasejson = $latestrelease.Content | ConvertFrom-Json $latestversion = $latestreleasejson.tag_name + ## Define update phrases and get a random one for the update info. $updateolderarray = @( "Jesus mate, you're out of date! Latest is $latestversion, go update.", @@ -41,6 +44,7 @@ $updatenewerarray = @( "You nutter, you're running a pre-release version! Latest is $latestversion!", "Bloody hell mate, this is unheard of, $currentversion isn't even released yet, latest is $latestversion!" ) + ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. If ($currentversion -lt $latestversion) { $footeraddition = (Get-Random -InputObject $updateolderarray -Count 1) From 774d2b6b398ba51fd072186e63ffdc779543123a Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 22:47:34 +0100 Subject: [PATCH 015/175] Numerous fixes - Invoke-RestMethod for notification no longer terminates the script when it fails. - Errors occuring during the Update-Success/Update-Fail/End-Script functions no longer terminate the script. - Console output noting script result is now based on $result variable. - End-Script function now ensures the script exits in all cases. - End-Script function now uses $PSCommandPath instead of $MyInvocation.MyCommand.Path. This resolves a null value error that previously occured in some cases. --- UpdateVeeamDiscordNotification.ps1 | 33 +++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index e0092fe..7cb47fa 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -66,19 +66,39 @@ function Update-Notification { } Write-Output 'Sending notification.' # Send iiit - Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' -ErrorAction Continue + Try { + Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + } + Catch { + Write-Output 'Update notification failed to send to Discord.' + } } # Success function function Update-Success { - Write-Output 'Successfully updated.' + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' + + # Set result var for notification and script output $result = 'Success!' - Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications-old –Recurse -Force + + # Remove copy of previously installed version + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force + + Write-Output "Update result: $result" Invoke-Expression Update-Notification Invoke-Expression End-Script } # Failure function function Update-Fail { + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' + + # Set result var for notification and script output $result = 'Failure!' + + # Take action based on the stage at which the error occured Switch ($fail) { download { Write-Output 'Failed to download update.' @@ -105,9 +125,7 @@ function Update-Fail { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications } } - Write-Output 'Update failed. Previous version restored.' - Remove-Item –Path $PSScriptRoot\VeeamDiscordNotifications –Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + Write-Output "Update result: $result" Invoke-Expression Update-Notification Invoke-Expression End-Script } @@ -119,7 +137,7 @@ function End-Script { Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" } Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' - Remove-Item -LiteralPath $MyInvocation.MyCommand.Path -Force + Remove-Item -LiteralPath $PSCommandPath -Force # Stop logging Write-Output 'Stop logging.' @@ -128,6 +146,7 @@ function End-Script { Write-Output 'Move log file.' Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" Write-Output 'Exiting.' + Exit } From dfad518729ff18f0ed97f539ab3a3ebd6e3999ce Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 23:12:26 +0100 Subject: [PATCH 016/175] Append current date to log file name --- UpdateVeeamDiscordNotification.ps1 | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 7cb47fa..0fcf7e5 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -5,8 +5,13 @@ Param ( # Import functions Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" -# Start logging -Start-Logging "$PSScriptRoot\update.log" + +# Logging +## Set log file name +$date = (Get-Date -UFormat %Y-%m-%d) +$logfile = "$PSScriptRoot\update_$date.log" +## Start logging to file +Start-Logging $logfile # Set error action preference. Write-Output 'Set error action preference.' @@ -141,10 +146,10 @@ function End-Script { # Stop logging Write-Output 'Stop logging.' - Stop-Logging "$PSScriptRoot\update.log" + Stop-Logging $logfile # Move log file - Write-Output 'Move log file.' - Move-Item "$PSScriptRoot\update.log" "$PSScriptRoot\VeeamDiscordNotifications\log\update.log" + Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' + Move-Item $logfile "$PSScriptRoot\VeeamDiscordNotifications\log\" Write-Output 'Exiting.' Exit } From eac53aa69454dbbf9260f9dcdded80d4068315e1 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 23:40:34 +0100 Subject: [PATCH 017/175] All logs are retained through updates --- UpdateVeeamDiscordNotification.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 0fcf7e5..aa9681a 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -87,6 +87,10 @@ function Update-Success { # Set result var for notification and script output $result = 'Success!' + # Copy logs directory from copy of previously installed version to new install + Write-Output 'Copying logs from old version to new version.' + Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse + # Remove copy of previously installed version Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force From aa6dfa81b83db344462f2765804c3ff61bf65db7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 23:42:14 +0100 Subject: [PATCH 018/175] Append time to log file name as well An oversight on my part was not adding the time when I added the date to the log file name a couple of commits ago. This is now resolved. --- UpdateVeeamDiscordNotification.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index aa9681a..7e78bf3 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -8,7 +8,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Logging ## Set log file name -$date = (Get-Date -UFormat %Y-%m-%d) +$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) $logfile = "$PSScriptRoot\update_$date.log" ## Start logging to file Start-Logging $logfile From 604a1a64bfdc01b61209914900c49373550b6d7b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 30 Jun 2019 23:50:56 +0100 Subject: [PATCH 019/175] Better comments, better output, fix log copy issue - Clarified/updated/improved some comments and console/log outputs. - Fixed an issue where logs would not copy from the old to new install correctly. --- UpdateVeeamDiscordNotification.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 7e78bf3..ab0d61b 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -89,12 +89,12 @@ function Update-Success { # Copy logs directory from copy of previously installed version to new install Write-Output 'Copying logs from old version to new version.' - Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse + Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force # Remove copy of previously installed version + Write-Output 'Removing old version.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - Write-Output "Update result: $result" Invoke-Expression Update-Notification Invoke-Expression End-Script } @@ -134,14 +134,13 @@ function Update-Fail { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications } } - Write-Output "Update result: $result" Invoke-Expression Update-Notification Invoke-Expression End-Script } # End of script function function End-Script { # Clean up. - Write-Output 'Remove downloaded ZIP if it''s still there.' + Write-Output 'Remove downloaded ZIP.' If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" } @@ -154,6 +153,8 @@ function End-Script { # Move log file Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' Move-Item $logfile "$PSScriptRoot\VeeamDiscordNotifications\log\" + # Report result and exit script + Write-Output "Update result: $result" Write-Output 'Exiting.' Exit } @@ -216,7 +217,7 @@ Catch { Invoke-Expression Update-Fail } -# Rename old version to make room for the new version +# Rename old version to keep as a backup while the update is in progress. Try { Write-Output 'Rename old version to make room for the new version.' Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old From c14e5b693c44a870a4f12b688d3cda3d4d8cba6f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 2 Jul 2019 15:44:50 +0100 Subject: [PATCH 020/175] Fixed Update-Notification function. Notifications no longer consistently fail. The issue was that $newversion and $oldversion were not strings. --- UpdateVeeamDiscordNotification.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index ab0d61b..dbf914f 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -75,7 +75,9 @@ function Update-Notification { Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' } Catch { - Write-Output 'Update notification failed to send to Discord.' + $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Warning 'Update notification failed to send to Discord.' + Write-Output "$errorvar" } } # Success function @@ -163,7 +165,7 @@ function End-Script { # Get currently downloaded version Try { Write-Output 'Getting currently downloaded version of the script.' - $oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" + [String]$oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() @@ -286,7 +288,7 @@ Catch { # Get newly downloaded version Try { Write-Output 'Get newly downloaded version.' - $newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" + [String]$newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() From 22eefe171581d7751dbbcd1eb3fd9d9f382b567b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 2 Jul 2019 15:54:43 +0100 Subject: [PATCH 021/175] Improve formatting & comments - Every mention of each var is case-consistent and some vars now include uppercase letters to improve readability. - Line breaks have been added or removed in some places where appropriate. --- UpdateVeeamDiscordNotification.ps1 | 119 +++++++++++++++-------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index dbf914f..ffda42f 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -9,9 +9,9 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Logging ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) -$logfile = "$PSScriptRoot\update_$date.log" +$logFile = "$PSScriptRoot\update_$date.log" ## Start logging to file -Start-Logging $logfile +Start-Logging $logFile # Set error action preference. Write-Output 'Set error action preference.' @@ -21,65 +21,66 @@ $ErrorActionPreference = 'Stop' function Update-Notification { Write-Output 'Building notification.' # Create embed and fields array - [System.Collections.ArrayList]$embedarray = @() - [System.Collections.ArrayList]$fieldarray = @() + [System.Collections.ArrayList]$embedArray = @() + [System.Collections.ArrayList]$fieldArray = @() # Thumbnail object - $thumbobject = [PSCustomObject]@{ - url = $currentconfig.thumbnail + $thumbObject = [PSCustomObject]@{ + url = $currentConfig.thumbnail } # Field objects - $resultfield = [PSCustomObject]@{ + $resultField = [PSCustomObject]@{ name = 'Update Result' value = $result inline = 'false' } - $newversionfield = [PSCustomObject]@{ + $newVersionField = [PSCustomObject]@{ name = 'New version' - value = $newversion + value = $newVersion inline = 'false' } - $oldversionfield = [PSCustomObject]@{ + $oldVersionField = [PSCustomObject]@{ name = 'Old version' - value = $oldversion + value = $oldVersion inline = 'false' } # Add field objects to the field array - $fieldarray.Add($oldversionfield) | Out-Null - $fieldarray.Add($newversionfield) | Out-Null - $fieldarray.Add($resultfield) | Out-Null + $fieldArray.Add($oldVersionField) | Out-Null + $fieldArray.Add($newVersionField) | Out-Null + $fieldArray.Add($resultField) | Out-Null # Send error if exist - If ($errorvar -ne $null) { - $errorfield = [PSCustomObject]@{ + If ($errorVar -ne $null) { + $errorField = [PSCustomObject]@{ name = 'Update Error' - value = $errorvar + value = $errorVar inline = 'false' } - $fieldarray.Add($errorfield) | Out-Null + $fieldArray.Add($errorField) | Out-Null } # Embed object including field and thumbnail vars from above - $embedobject = [PSCustomObject]@{ + $embedObject = [PSCustomObject]@{ title = 'Update' color = '1267393' - thumbnail = $thumbobject - fields = $fieldarray + thumbnail = $thumbObject + fields = $fieldArray } # Add embed object to the array created above - $embedarray.Add($embedobject) | Out-Null + $embedArray.Add($embedObject) | Out-Null # Build payload $payload = [PSCustomObject]@{ - embeds = $embedarray + embeds = $embedArray } Write-Output 'Sending notification.' # Send iiit Try { - Invoke-RestMethod -Uri $currentconfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Warning 'Update notification failed to send to Discord.' - Write-Output "$errorvar" + Write-Output "$errorVar" } } + # Success function function Update-Success { # Set error action preference so that errors while ending the script don't end the script prematurely. @@ -96,10 +97,12 @@ function Update-Success { # Remove copy of previously installed version Write-Output 'Removing old version.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - + + # Trigger the Update-Notification function and then End-Script function. Invoke-Expression Update-Notification Invoke-Expression End-Script } + # Failure function function Update-Fail { # Set error action preference so that errors while ending the script don't end the script prematurely. @@ -136,9 +139,12 @@ function Update-Fail { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications } } + + # Trigger the Update-Notification function and then End-Script function. Invoke-Expression Update-Notification Invoke-Expression End-Script } + # End of script function function End-Script { # Clean up. @@ -151,36 +157,37 @@ function End-Script { # Stop logging Write-Output 'Stop logging.' - Stop-Logging $logfile + Stop-Logging $logFile + # Move log file Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' - Move-Item $logfile "$PSScriptRoot\VeeamDiscordNotifications\log\" + Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" + # Report result and exit script Write-Output "Update result: $result" Write-Output 'Exiting.' Exit } - -# Get currently downloaded version +# Pull current config to variable Try { - Write-Output 'Getting currently downloaded version of the script.' - [String]$oldversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw + Write-Output 'Pull current config to variable.' + $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" Invoke-Expression Update-Fail } -# Pull current config to variable +# Get currently downloaded version Try { - Write-Output 'Pull current config to variable.' - $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json + Write-Output 'Getting currently downloaded version of the script.' + [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" Invoke-Expression Update-Fail } @@ -201,8 +208,8 @@ Try { Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'download' Invoke-Expression Update-Fail } @@ -213,8 +220,8 @@ Try { Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'unzip' Invoke-Expression Update-Fail } @@ -225,8 +232,8 @@ Try { Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'rename_old' Invoke-Expression Update-Fail } @@ -237,8 +244,8 @@ Try { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'rename_new' Invoke-Expression Update-Fail } @@ -249,8 +256,8 @@ Try { $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'after_rename_new' Invoke-Expression Update-Fail } @@ -279,8 +286,8 @@ Try { ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'after_rename_new' Invoke-Expression Update-Fail } @@ -288,17 +295,17 @@ Catch { # Get newly downloaded version Try { Write-Output 'Get newly downloaded version.' - [String]$newversion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw + [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { - $errorvar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorvar" + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" $fail = 'after_rename_new' Invoke-Expression Update-Fail } # Send notification -If ($newversion -eq $LatestVersion) { +If ($newVersion -eq $LatestVersion) { Invoke-Expression Update-Success } Else { From bc8d35bf19994c77531e4ad3b5253bbc6300a292 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 2 Jul 2019 16:45:19 +0100 Subject: [PATCH 022/175] Write warnings on failure Failure information is now written as a warning rather than standard output. --- UpdateVeeamDiscordNotification.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index ffda42f..3fb45a3 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -115,25 +115,25 @@ function Update-Fail { # Take action based on the stage at which the error occured Switch ($fail) { download { - Write-Output 'Failed to download update.' + Write-Warning 'Failed to download update.' } unzip { - Write-Output 'Failed to unzip update. Cleaning up and reverting.' + Write-Warning 'Failed to unzip update. Cleaning up and reverting.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force } rename_old { - Write-Output 'Failed to rename old version. Cleaning up and reverting.' + Write-Warning 'Failed to rename old version. Cleaning up and reverting.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force } rename_new { - Write-Output 'Failed to rename new version. Cleaning up and reverting.' + Write-Warning 'Failed to rename new version. Cleaning up and reverting.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications } after_rename_new { - Write-Output 'Failed after renaming new version. Cleaning up and reverting.' + Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications From 789f31e3a693719f5a17a7f16d637833a2e2570f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 2 Jul 2019 16:59:56 +0100 Subject: [PATCH 023/175] Edited and added version announcement phrases. --- DiscordVeeamAlertSender.ps1 | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 4af6ddd..a74eb69 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -27,22 +27,27 @@ $latestrelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscor $latestreleasejson = $latestrelease.Content | ConvertFrom-Json $latestversion = $latestreleasejson.tag_name -## Define update phrases and get a random one for the update info. +## Define version announcement phrases and get a random one for the version info in the footer of the report. $updateolderarray = @( - "Jesus mate, you're out of date! Latest is $latestversion, go update.", - "Bloody hell you muppet, you need to update! Latest is $latestversion, go update.", - "Fuck me sideways, you're out of date! Latest is $latestversion, go update." + "Jesus mate, you're out of date! Latest is $latestversion. Check your update logs.", + "Bloody hell you muppet, you need to update! Latest is $latestversion. Check your update logs.", + "Fuck me sideways, you're out of date! Latest is $latestversion. Check your update logs.", + "Shitting heck lad, you need to update! Latest is $latestversion. Check your update logs.", + "Christ almighty, you're out of date! Latest is $latestversion. Check your update logs." ) $updatecurrentarray = @( "Nice work mate, you're up to date.", "Good shit buddy, you're up to date.", - "Top stuff my dude, you're up to date." + "Top stuff my dude, you're running the latest version.", + "Good job fam, you're all up to date.", + "Lovely stuff mate, you're running the latest version." ) $updatenewerarray = @( "Wewlad, check you out running a pre-release version, latest is $latestversion!", "Christ m8e, this is mental, you're ahead of release, latest is $latestversion!", "You nutter, you're running a pre-release version! Latest is $latestversion!", "Bloody hell mate, this is unheard of, $currentversion isn't even released yet, latest is $latestversion!" + "Fuuuckin hell, $currentversion hasn't even been released! Latest is $latestrelease." ) ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. From f7d1fa8bbd05f7bac389ec459a17a57d82eb5d37 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jul 2019 14:06:25 +0100 Subject: [PATCH 024/175] Job duration field improvement Job duration is now reported as [d] h m s, rather than hh mm ss. Days are only mentioned if the job exceeds one day, otherwise it is always h m s. --- DiscordVeeamAlertSender.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index a74eb69..c93a70b 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -194,7 +194,10 @@ If ($SpeedRound -eq '0 B/s') { # Calculate job duration $Duration = $session.Info.EndTime - $session.Info.CreationTime $TimeSpan = $Duration -$Duration = '{0:00}h {1:00}m {2:00}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds +If ($TimeSpan.Days -ge '1') { + $Duration = $TimeSpan.Days+'d ' +} +$Duration += '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds # Switch on the session status switch ($Status) { From 2ce53089b960e6203516a952c60ebd32b8566fc7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jul 2019 15:59:41 +0100 Subject: [PATCH 025/175] Readability improvements Vars are now capitalised where necessary to improve readability. --- DiscordNotificationBootstrap.ps1 | 14 +- DiscordVeeamAlertSender.ps1 | 244 +++++++++++++++---------------- 2 files changed, 129 insertions(+), 129 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 0e81a74..506a4ac 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -13,19 +13,19 @@ if($config.debug_log) { Add-PSSnapin VeeamPSSnapin # Get the Veeam job from parent process. -$parentpid = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() -$parentcmd = (Get-WmiObject Win32_Process -Filter "processid='$parentpid'").CommandLine -$job = Get-VBRJob | ?{$parentcmd -like "*"+$_.Id.ToString()+"*"} +$parentPID = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() +$parentCmd = (Get-WmiObject Win32_Process -Filter "processid='$parentPID'").CommandLine +$job = Get-VBRJob | ?{$parentCmd -like "*"+$_.Id.ToString()+"*"} # Get the Veeam session. -$session = Get-VBRBackupSession | ?{($_.OrigJobName -eq $job.Name) -and ($parentcmd -like "*"+$_.Id.ToString()+"*")} +$session = Get-VBRBackupSession | ?{($_.OrigJobName -eq $job.Name) -and ($parentCmd -like "*"+$_.Id.ToString()+"*")} # Store the job's name and ID. -$Id = '"' + $session.Id.ToString().ToString().Trim() + '"' -$JobName = '"' + $session.OrigJobName.ToString().Trim() + '"' +$id = '"' + $session.Id.ToString().ToString().Trim() + '"' +$jobName = '"' + $session.OrigJobName.ToString().Trim() + '"' # Build argument string for the alert sender. -$powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $JobName", "-Id $Id" +$powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $id" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session so that we can gather information from the completed job. diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index c93a70b..d420521 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -1,7 +1,7 @@ # Pull in variables that were set when the script was started by Veeam Param( - [String]$JobName, - [String]$Id + [String]$jobName, + [String]$id ) # Import Functions @@ -17,190 +17,190 @@ if($config.debug_log) { # Determine if an update is required. ## Get currently downloaded version of this project. -$currentversion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw +$currentVersion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw ## Get latest release from GitHub and use that to determine the latest version. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$latestrelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing +$latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing ## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"} so we need to extract tag_name. -$latestreleasejson = $latestrelease.Content | ConvertFrom-Json -$latestversion = $latestreleasejson.tag_name +$latestReleaseJson = $latestRelease.Content | ConvertFrom-Json +$latestVersion = $latestReleaseJson.tag_name ## Define version announcement phrases and get a random one for the version info in the footer of the report. -$updateolderarray = @( - "Jesus mate, you're out of date! Latest is $latestversion. Check your update logs.", - "Bloody hell you muppet, you need to update! Latest is $latestversion. Check your update logs.", - "Fuck me sideways, you're out of date! Latest is $latestversion. Check your update logs.", - "Shitting heck lad, you need to update! Latest is $latestversion. Check your update logs.", - "Christ almighty, you're out of date! Latest is $latestversion. Check your update logs." +$updateOlderArray = @( + "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", + "Bloody hell you muppet, you need to update! Latest is $latestVersion. Check your update logs.", + "Fuck me sideways, you're out of date! Latest is $latestVersion. Check your update logs.", + "Shitting heck lad, you need to update! Latest is $latestVersion. Check your update logs.", + "Christ almighty, you're out of date! Latest is $latestVersion. Check your update logs." ) -$updatecurrentarray = @( +$updateCurrentArray = @( "Nice work mate, you're up to date.", "Good shit buddy, you're up to date.", "Top stuff my dude, you're running the latest version.", "Good job fam, you're all up to date.", "Lovely stuff mate, you're running the latest version." ) -$updatenewerarray = @( - "Wewlad, check you out running a pre-release version, latest is $latestversion!", - "Christ m8e, this is mental, you're ahead of release, latest is $latestversion!", - "You nutter, you're running a pre-release version! Latest is $latestversion!", - "Bloody hell mate, this is unheard of, $currentversion isn't even released yet, latest is $latestversion!" - "Fuuuckin hell, $currentversion hasn't even been released! Latest is $latestrelease." +$updateNewerArray = @( + "Wewlad, check you out running a pre-release version, latest is $latestVersion!", + "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", + "You nutter, you're running a pre-release version! Latest is $latestVersion!", + "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!" + "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestRelease." ) ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. -If ($currentversion -lt $latestversion) { - $footeraddition = (Get-Random -InputObject $updateolderarray -Count 1) +If ($currentVersion -lt $latestVersion) { + $footerAddition = (Get-Random -InputObject $updateOlderArray -Count 1) } -Elseif ($currentversion -eq $latestversion) { - $footeraddition = (Get-Random -InputObject $updatecurrentarray -Count 1) +Elseif ($currentVersion -eq $latestVersion) { + $footerAddition = (Get-Random -InputObject $updateCurrentArray -Count 1) } -Elseif ($currentversion -gt $latestversion) { - $footeraddition = (Get-Random -InputObject $updatenewerarray -Count 1) +Elseif ($currentVersion -gt $latestVersion) { + $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) } # Add Veeam snap-in Add-PSSnapin VeeamPSSnapin # Get the session -$session = Get-VBRBackupSession | ?{($_.OrigJobName -eq $JobName) -and ($Id -eq $_.Id.ToString())} +$session = Get-VBRBackupSession | ?{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} # Wait for the session to finish up while ($session.IsCompleted -eq $false) { Write-LogMessage 'Info' 'Session not finished Sleeping...' Start-Sleep -m 200 - $session = Get-VBRBackupSession | ?{($_.OrigJobName -eq $JobName) -and ($Id -eq $_.Id.ToString())} + $session = Get-VBRBackupSession | ?{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} } # Gather session info -[String]$Status = $session.Result -$JobName = $session.Name.ToString().Trim() +[String]$status = $session.Result +$jobName = $session.Name.ToString().Trim() $JobType = $session.JobTypeString.Trim() -[Float]$JobSize = $session.BackupStats.DataSize -[Float]$TransfSize = $session.BackupStats.BackupSize -[Float]$Speed = $session.Info.Progress.AvgSpeed +[Float]$jobSize = $session.BackupStats.DataSize +[Float]$transferSize = $session.BackupStats.BackupSize +[Float]$speed = $session.Info.Progress.AvgSpeed # Determine whether to report the job and actual data sizes in B, KB, MB, GB, or TB, depending on completed size. Will fallback to B[ytes] if no match. ## Job size -Switch ($JobSize) { +Switch ($jobSize) { ({$PSItem -lt 1KB}) { - [String]$JobSizeRound = $JobSize - $JobSizeRound += ' B' + [String]$jobSizeRound = $jobSize + $jobSizeRound += ' B' break } ({$PSItem -lt 1MB}) { - $JobSize = $JobSize / 1KB - [String]$JobSizeRound = [math]::Round($JobSize,2) - $JobSizeRound += ' KB' + $jobSize = $jobSize / 1KB + [String]$jobSizeRound = [math]::Round($jobSize,2) + $jobSizeRound += ' KB' break } ({$PSItem -lt 1GB}) { - $JobSize = $JobSize / 1MB - [String]$JobSizeRound = [math]::Round($JobSize,2) - $JobSizeRound += ' MB' + $jobSize = $jobSize / 1MB + [String]$jobSizeRound = [math]::Round($jobSize,2) + $jobSizeRound += ' MB' break } ({$PSItem -lt 1TB}) { - $JobSize = $JobSize / 1GB - [String]$JobSizeRound = [math]::Round($JobSize,2) - $JobSizeRound += ' GB' + $jobSize = $jobSize / 1GB + [String]$jobSizeRound = [math]::Round($jobSize,2) + $jobSizeRound += ' GB' break } ({$PSItem -ge 1TB}) { - $JobSize = $JobSize / 1TB - [String]$JobSizeRound = [math]::Round($JobSize,2) - $JobSizeRound += ' TB' + $jobSize = $jobSize / 1TB + [String]$jobSizeRound = [math]::Round($jobSize,2) + $jobSizeRound += ' TB' break } Default { - [String]$JobSizeRound = $JobSize - $JobSizeRound += ' B' + [String]$jobSizeRound = $jobSize + $jobSizeRound += ' B' } } ## Transfer size -Switch ($TransfSize) { +Switch ($transferSize) { ({$PSItem -lt 1KB}) { - [String]$TransfSizeRound = $TransfSize - $TransfSizeRound += ' B' + [String]$transferSizeRound = $transferSize + $transferSizeRound += ' B' break } ({$PSItem -lt 1MB}) { - $TransfSize = $TransfSize / 1KB - [String]$TransfSizeRound = [math]::Round($TransfSize,2) - $TransfSizeRound += ' KB' + $transferSize = $transferSize / 1KB + [String]$transferSizeRound = [math]::Round($transferSize,2) + $transferSizeRound += ' KB' break } ({$PSItem -lt 1GB}) { - $TransfSize = $TransfSize / 1MB - [String]$TransfSizeRound = [math]::Round($TransfSize,2) - $TransfSizeRound += ' MB' + $transferSize = $transferSize / 1MB + [String]$transferSizeRound = [math]::Round($transferSize,2) + $transferSizeRound += ' MB' break } ({$PSItem -lt 1TB}) { - $TransfSize = $TransfSize / 1GB - [String]$TransfSizeRound = [math]::Round($TransfSize,2) - $TransfSizeRound += ' GB' + $transferSize = $transferSize / 1GB + [String]$transferSizeRound = [math]::Round($transferSize,2) + $transferSizeRound += ' GB' break } ({$PSItem -ge 1TB}) { - $TransfSize = $TransfSize / 1TB - [String]$TransfSizeRound = [math]::Round($TransfSize,2) - $TransfSizeRound += ' TB' + $transferSize = $transferSize / 1TB + [String]$transferSizeRound = [math]::Round($transferSize,2) + $transferSizeRound += ' TB' break } Default { - [String]$TransfSizeRound = $TransfSize - $TransfSizeRound += ' B' + [String]$transferSizeRound = $transferSize + $transferSizeRound += ' B' } } # Determine whether to report the job processing rate in B/s, KB/s, MB/s, or GB/s, depending on the figure. Will fallback to B[ytes] if no match. -Switch ($Speed) { +Switch ($speed) { ({$PSItem -lt 1KB}) { - [String]$SpeedRound = $Speed - $SpeedRound += ' B/s' + [String]$speedRound = $speed + $speedRound += ' B/s' break } ({$PSItem -lt 1MB}) { - $Speed = $Speed / 1KB - [String]$SpeedRound = [math]::Round($Speed,2) - $SpeedRound += ' KB/s' + $speed = $speed / 1KB + [String]$speedRound = [math]::Round($speed,2) + $speedRound += ' KB/s' break } ({$PSItem -lt 1GB}) { - $Speed = $Speed / 1MB - [String]$SpeedRound = [math]::Round($Speed,2) - $SpeedRound += ' MB/s' + $speed = $speed / 1MB + [String]$speedRound = [math]::Round($speed,2) + $speedRound += ' MB/s' break } ({$PSItem -lt 1TB}) { - $Speed = $Speed / 1GB - [String]$SpeedRound = [math]::Round($Speed,2) - $SpeedRound += ' GB/s' + $speed = $speed / 1GB + [String]$speedRound = [math]::Round($speed,2) + $speedRound += ' GB/s' break } Default { - [String]$SpeedRound = $Speed - $SpeedRound += ' B/s' + [String]$speedRound = $speed + $speedRound += ' B/s' } } # Write "Unknown" processing speed if 0B/s to avoid confusion. -If ($SpeedRound -eq '0 B/s') { - $SpeedRound = 'Unknown.' +If ($speedRound -eq '0 B/s') { + $speedRound = 'Unknown.' } # Calculate job duration -$Duration = $session.Info.EndTime - $session.Info.CreationTime -$TimeSpan = $Duration -If ($TimeSpan.Days -ge '1') { - $Duration = $TimeSpan.Days+'d ' +$duration = $session.Info.EndTime - $session.Info.CreationTime +$timeSpan = $duration +If ($timeSpan.Days -ge '1') { + $duration = $timeSpan.Days+'d ' } -$Duration += '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds +$duration += '{0}h {1}m {2}s' -f $timeSpan.Hours, $timeSpan.Minutes, $timeSpan.Seconds # Switch on the session status -switch ($Status) { +switch ($status) { None {$colour = '16777215'} Warning {$colour = '16776960'} Success {$colour = '65280'} @@ -209,85 +209,85 @@ switch ($Status) { } # Create embed and fields array -[System.Collections.ArrayList]$embedarray = @() -[System.Collections.ArrayList]$fieldarray = @() +[System.Collections.ArrayList]$embedArray = @() +[System.Collections.ArrayList]$fieldArray = @() # Thumbnail object -$thumbobject = [PSCustomObject]@{ +$thumbObject = [PSCustomObject]@{ url = $config.thumbnail } # Field objects -$backupsizefield = [PSCustomObject]@{ +$backupSizeField = [PSCustomObject]@{ name = 'Backup size' - value = [String]$JobSizeRound + value = [String]$jobSizeRound inline = 'true' } -$transfsizefield = [PSCustomObject]@{ +$transferSizeField = [PSCustomObject]@{ name = 'Transferred Data' - value = [String]$TransfSizeRound + value = [String]$transferSizeRound inline = 'true' } -$dedupfield = [PSCustomObject]@{ +$dedupField = [PSCustomObject]@{ name = 'Dedup Ratio' value = [String]$session.BackupStats.DedupRatio inline = 'true' } -$compressfield = [PSCustomObject]@{ +$compressField = [PSCustomObject]@{ name = 'Compression Ratio' value = [String]$session.BackupStats.CompressRatio inline = 'true' } -$durationfield = [PSCustomObject]@{ +$durationField = [PSCustomObject]@{ name = 'Job Duration' - value = $Duration + value = $duration inline = 'true' } -$speedfield = [PSCustomObject]@{ +$speedField = [PSCustomObject]@{ name = 'Processing rate' - value = $SpeedRound + value = $speedRound inline = 'true' } # Add field objects to the field array -$fieldarray.Add($backupsizefield) | Out-Null -$fieldarray.Add($transfsizefield) | Out-Null -$fieldarray.Add($dedupfield) | Out-Null -$fieldarray.Add($compressfield) | Out-Null -$fieldarray.Add($durationfield) | Out-Null -$fieldarray.Add($speedfield) | Out-Null +$fieldArray.Add($backupSizeField) | Out-Null +$fieldArray.Add($transferSizeField) | Out-Null +$fieldArray.Add($dedupField) | Out-Null +$fieldArray.Add($compressField) | Out-Null +$fieldArray.Add($durationField) | Out-Null +$fieldArray.Add($speedField) | Out-Null # Build footer object -$footerobject = [PSCustomObject]@{ - text = "tigattack's VeeamDiscordNotifications $currentversion. $footeraddition" +$footerObject = [PSCustomObject]@{ + text = "tigattack's VeeamDiscordNotifications $currentVersion. $footerAddition" icon_url = 'https://avatars0.githubusercontent.com/u/10629864' } # Embed object including field and thumbnail vars from above -$embedobject = [PSCustomObject]@{ - title = $JobName - description = $Status +$embedObject = [PSCustomObject]@{ + title = $jobName + description = $status color = $colour - thumbnail = $thumbobject - fields = $fieldarray - footer = $footerobject + thumbnail = $thumbObject + fields = $fieldArray + footer = $footerObject } # Add embed object to the array created above -$embedarray.Add($embedobject) | Out-Null +$embedArray.Add($embedObject) | Out-Null # Create payload ## Mention user if job failed -If ($config.mention_on_fail -and $Status -eq 'Failed') { +If ($config.mention_on_fail -and $status -eq 'Failed') { $payload = [PSCustomObject]@{ - content = "<@!$($config.userid)> Job status $Status" - embeds = $embedarray + content = "<@!$($config.userid)> Job status $status" + embeds = $embedArray } } ## Otherwise do not mention user Else { $payload = [PSCustomObject]@{ - embeds = $embedarray + embeds = $embedArray } } @@ -295,10 +295,10 @@ Else { $request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' # Trigger update on outdated version -If ($currentversion -lt $latestversion -and $config.auto_update) { +If ($currentVersion -lt $latestVersion -and $config.auto_update) { Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 - $powershellArguments = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestversion" + $powershellArguments = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden } From 37f9e0b8ccf4f6d2f4565f26d2d255454fc6b09d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jul 2019 16:02:21 +0100 Subject: [PATCH 026/175] Fixed job duration calculation --- DiscordVeeamAlertSender.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index d420521..745e40f 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -192,12 +192,13 @@ If ($speedRound -eq '0 B/s') { } # Calculate job duration -$duration = $session.Info.EndTime - $session.Info.CreationTime -$timeSpan = $duration -If ($timeSpan.Days -ge '1') { - $duration = $timeSpan.Days+'d ' +$TimeSpan = $session.Info.EndTime - $session.Info.CreationTime +If ($TimeSpan.Days -ge '1') { + $Duration = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds +} +Else { + $Duration = '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds } -$duration += '{0}h {1}m {2}s' -f $timeSpan.Hours, $timeSpan.Minutes, $timeSpan.Seconds # Switch on the session status switch ($status) { From a2c1f46801bad8f82abe233d3350be35bf61e81b Mon Sep 17 00:00:00 2001 From: "Daniel T. Thorpe" Date: Tue, 14 Jan 2020 08:32:17 +0000 Subject: [PATCH 027/175] fix(discordveeamalertsender.ps1): correct latestRelease to latestVersion fixed raw JSON being dumped into the notification text, should fix weird formatting when this string is triggered --- DiscordVeeamAlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 745e40f..085851a 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -47,7 +47,7 @@ $updateNewerArray = @( "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", "You nutter, you're running a pre-release version! Latest is $latestVersion!", "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!" - "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestRelease." + "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." ) ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. From 00b88fbd42358e99307b9cd9f9094972fa9e46c3 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 12 May 2020 15:28:18 +0100 Subject: [PATCH 028/175] Replace ? with Where-Object. --- DiscordNotificationBootstrap.ps1 | 4 ++-- DiscordVeeamAlertSender.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 506a4ac..3961624 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -15,10 +15,10 @@ Add-PSSnapin VeeamPSSnapin # Get the Veeam job from parent process. $parentPID = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() $parentCmd = (Get-WmiObject Win32_Process -Filter "processid='$parentPID'").CommandLine -$job = Get-VBRJob | ?{$parentCmd -like "*"+$_.Id.ToString()+"*"} +$job = Get-VBRJob | Where-Object{$parentCmd -like "*"+$_.Id.ToString()+"*"} # Get the Veeam session. -$session = Get-VBRBackupSession | ?{($_.OrigJobName -eq $job.Name) -and ($parentCmd -like "*"+$_.Id.ToString()+"*")} +$session = Get-VBRBackupSession | Where-Object{($_.OrigJobName -eq $job.Name) -and ($parentCmd -like "*"+$_.Id.ToString()+"*")} # Store the job's name and ID. $id = '"' + $session.Id.ToString().ToString().Trim() + '"' diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 745e40f..1193f43 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -65,13 +65,13 @@ Elseif ($currentVersion -gt $latestVersion) { Add-PSSnapin VeeamPSSnapin # Get the session -$session = Get-VBRBackupSession | ?{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} +$session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} # Wait for the session to finish up while ($session.IsCompleted -eq $false) { Write-LogMessage 'Info' 'Session not finished Sleeping...' Start-Sleep -m 200 - $session = Get-VBRBackupSession | ?{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} + $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} } # Gather session info From 0932de7d70c1fd4b6a9753bfb3c934fe0fb33aa0 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 12 May 2020 15:31:53 +0100 Subject: [PATCH 029/175] Fixed logging for running backup session. --- DiscordVeeamAlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 1193f43..0b29771 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -69,7 +69,7 @@ $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -an # Wait for the session to finish up while ($session.IsCompleted -eq $false) { - Write-LogMessage 'Info' 'Session not finished Sleeping...' + Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} } From f46edfa6024345e94a83489a22622b8e92b9ac53 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 12 May 2020 15:34:36 +0100 Subject: [PATCH 030/175] Fixed PossibleIncorrectComparisonWithNull. --- UpdateVeeamDiscordNotification.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 3fb45a3..4464ed1 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -48,7 +48,7 @@ function Update-Notification { $fieldArray.Add($newVersionField) | Out-Null $fieldArray.Add($resultField) | Out-Null # Send error if exist - If ($errorVar -ne $null) { + If ($null -ne $errorVar) { $errorField = [PSCustomObject]@{ name = 'Update Error' value = $errorVar @@ -86,7 +86,7 @@ function Update-Success { # Set error action preference so that errors while ending the script don't end the script prematurely. Write-Output 'Set error action preference.' $ErrorActionPreference = 'Continue' - + # Set result var for notification and script output $result = 'Success!' @@ -97,7 +97,7 @@ function Update-Success { # Remove copy of previously installed version Write-Output 'Removing old version.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - + # Trigger the Update-Notification function and then End-Script function. Invoke-Expression Update-Notification Invoke-Expression End-Script @@ -108,7 +108,7 @@ function Update-Fail { # Set error action preference so that errors while ending the script don't end the script prematurely. Write-Output 'Set error action preference.' $ErrorActionPreference = 'Continue' - + # Set result var for notification and script output $result = 'Failure!' @@ -154,7 +154,7 @@ function End-Script { } Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' Remove-Item -LiteralPath $PSCommandPath -Force - + # Stop logging Write-Output 'Stop logging.' Stop-Logging $logFile From 74f760385736ea4bb864926a583186cef0bd61d5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 12 May 2020 16:11:36 +0100 Subject: [PATCH 031/175] Fixed unapproved verb. --- UpdateVeeamDiscordNotification.ps1 | 626 ++++++++++++++--------------- 1 file changed, 313 insertions(+), 313 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 4464ed1..aca5b1e 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -1,313 +1,313 @@ -# Pull version from script trigger -Param ( - [string]$LatestVersion -) - -# Import functions -Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" - -# Logging -## Set log file name -$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) -$logFile = "$PSScriptRoot\update_$date.log" -## Start logging to file -Start-Logging $logFile - -# Set error action preference. -Write-Output 'Set error action preference.' -$ErrorActionPreference = 'Stop' - -# Notification function -function Update-Notification { - Write-Output 'Building notification.' - # Create embed and fields array - [System.Collections.ArrayList]$embedArray = @() - [System.Collections.ArrayList]$fieldArray = @() - # Thumbnail object - $thumbObject = [PSCustomObject]@{ - url = $currentConfig.thumbnail - } - # Field objects - $resultField = [PSCustomObject]@{ - name = 'Update Result' - value = $result - inline = 'false' - } - $newVersionField = [PSCustomObject]@{ - name = 'New version' - value = $newVersion - inline = 'false' - } - $oldVersionField = [PSCustomObject]@{ - name = 'Old version' - value = $oldVersion - inline = 'false' - } - # Add field objects to the field array - $fieldArray.Add($oldVersionField) | Out-Null - $fieldArray.Add($newVersionField) | Out-Null - $fieldArray.Add($resultField) | Out-Null - # Send error if exist - If ($null -ne $errorVar) { - $errorField = [PSCustomObject]@{ - name = 'Update Error' - value = $errorVar - inline = 'false' - } - $fieldArray.Add($errorField) | Out-Null - } - # Embed object including field and thumbnail vars from above - $embedObject = [PSCustomObject]@{ - title = 'Update' - color = '1267393' - thumbnail = $thumbObject - fields = $fieldArray - } - # Add embed object to the array created above - $embedArray.Add($embedObject) | Out-Null - # Build payload - $payload = [PSCustomObject]@{ - embeds = $embedArray - } - Write-Output 'Sending notification.' - # Send iiit - Try { - Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' - } - Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Warning 'Update notification failed to send to Discord.' - Write-Output "$errorVar" - } -} - -# Success function -function Update-Success { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' - - # Set result var for notification and script output - $result = 'Success!' - - # Copy logs directory from copy of previously installed version to new install - Write-Output 'Copying logs from old version to new version.' - Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force - - # Remove copy of previously installed version - Write-Output 'Removing old version.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script -} - -# Failure function -function Update-Fail { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' - - # Set result var for notification and script output - $result = 'Failure!' - - # Take action based on the stage at which the error occured - Switch ($fail) { - download { - Write-Warning 'Failed to download update.' - } - unzip { - Write-Warning 'Failed to unzip update. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - } - rename_old { - Write-Warning 'Failed to rename old version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force - } - rename_new { - Write-Warning 'Failed to rename new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - after_rename_new { - Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - } - - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script -} - -# End of script function -function End-Script { - # Clean up. - Write-Output 'Remove downloaded ZIP.' - If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { - Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" - } - Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' - Remove-Item -LiteralPath $PSCommandPath -Force - - # Stop logging - Write-Output 'Stop logging.' - Stop-Logging $logFile - - # Move log file - Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' - Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" - - # Report result and exit script - Write-Output "Update result: $result" - Write-Output 'Exiting.' - Exit -} - -# Pull current config to variable -Try { - Write-Output 'Pull current config to variable.' - $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - Invoke-Expression Update-Fail -} - -# Get currently downloaded version -Try { - Write-Output 'Getting currently downloaded version of the script.' - [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - Invoke-Expression Update-Fail -} - -# Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. -while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { - $timer++ - Start-Sleep -Seconds 1 - If ($timer -eq '60') { - Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' - } - Invoke-Expression Update-Fail -} - -# Pull latest version of script from GitHub -Try { - Write-Output 'Pull latest version of script from GitHub.' - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'download' - Invoke-Expression Update-Fail -} - -# Expand downloaded ZIP -Try { - Write-Output 'Expand downloaded ZIP.' - Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'unzip' - Invoke-Expression Update-Fail -} - -# Rename old version to keep as a backup while the update is in progress. -Try { - Write-Output 'Rename old version to make room for the new version.' - Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'rename_old' - Invoke-Expression Update-Fail -} - -# Rename extracted update -Try { - Write-Output 'Rename extracted update.' - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'rename_new' - Invoke-Expression Update-Fail -} - -# Pull configuration from new conf file -Try { - Write-Output 'Pull configuration from new conf file.' - $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail -} - -# Unblock script files -Write-Output 'Unblock script files.' -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotification.ps1 -ErrorAction Continue - -# Populate conf.json with previous configuration -Try { - Write-Output 'Populate conf.json with previous configuration.' - $newConfig.webhook = $currentConfig.webhook - $newConfig.userid = $currentConfig.userid - if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { - $newConfig.mention_on_fail = $currentConfig.mention_on_fail - } - if ($currentConfig.debug_log -ne $newConfig.debug_log) { - $newConfig.debug_log = $currentConfig.debug_log - } - if ($currentConfig.auto_update -ne $newConfig.auto_update) { - $newConfig.auto_update = $currentConfig.auto_update - } - ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail -} - -# Get newly downloaded version -Try { - Write-Output 'Get newly downloaded version.' - [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw -} -Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail -} - -# Send notification -If ($newVersion -eq $LatestVersion) { - Invoke-Expression Update-Success -} -Else { - Invoke-Expression Update-Fail -} +# Pull version from script trigger +Param ( + [string]$LatestVersion +) + +# Import functions +Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" + +# Logging +## Set log file name +$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) +$logFile = "$PSScriptRoot\update_$date.log" +## Start logging to file +Start-Logging $logFile + +# Set error action preference. +Write-Output 'Set error action preference.' +$ErrorActionPreference = 'Stop' + +# Notification function +function Update-Notification { + Write-Output 'Building notification.' + # Create embed and fields array + [System.Collections.ArrayList]$embedArray = @() + [System.Collections.ArrayList]$fieldArray = @() + # Thumbnail object + $thumbObject = [PSCustomObject]@{ + url = $currentConfig.thumbnail + } + # Field objects + $resultField = [PSCustomObject]@{ + name = 'Update Result' + value = $result + inline = 'false' + } + $newVersionField = [PSCustomObject]@{ + name = 'New version' + value = $newVersion + inline = 'false' + } + $oldVersionField = [PSCustomObject]@{ + name = 'Old version' + value = $oldVersion + inline = 'false' + } + # Add field objects to the field array + $fieldArray.Add($oldVersionField) | Out-Null + $fieldArray.Add($newVersionField) | Out-Null + $fieldArray.Add($resultField) | Out-Null + # Send error if exist + If ($null -ne $errorVar) { + $errorField = [PSCustomObject]@{ + name = 'Update Error' + value = $errorVar + inline = 'false' + } + $fieldArray.Add($errorField) | Out-Null + } + # Embed object including field and thumbnail vars from above + $embedObject = [PSCustomObject]@{ + title = 'Update' + color = '1267393' + thumbnail = $thumbObject + fields = $fieldArray + } + # Add embed object to the array created above + $embedArray.Add($embedObject) | Out-Null + # Build payload + $payload = [PSCustomObject]@{ + embeds = $embedArray + } + Write-Output 'Sending notification.' + # Send iiit + Try { + Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + } + Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Warning 'Update notification failed to send to Discord.' + Write-Output "$errorVar" + } +} + +# Success function +function Update-Success { + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' + + # Set result var for notification and script output + $result = 'Success!' + + # Copy logs directory from copy of previously installed version to new install + Write-Output 'Copying logs from old version to new version.' + Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force + + # Remove copy of previously installed version + Write-Output 'Removing old version.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force + + # Trigger the Update-Notification function and then End-Script function. + Invoke-Expression Update-Notification + Invoke-Expression End-Script +} + +# Failure function +function Update-Fail { + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' + + # Set result var for notification and script output + $result = 'Failure!' + + # Take action based on the stage at which the error occured + Switch ($fail) { + download { + Write-Warning 'Failed to download update.' + } + unzip { + Write-Warning 'Failed to unzip update. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + } + rename_old { + Write-Warning 'Failed to rename old version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + } + rename_new { + Write-Warning 'Failed to rename new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + after_rename_new { + Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + } + + # Trigger the Update-Notification function and then End-Script function. + Invoke-Expression Update-Notification + Invoke-Expression End-Script +} + +# End of script function +function Stop-Script { + # Clean up. + Write-Output 'Remove downloaded ZIP.' + If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { + Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" + } + Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' + Remove-Item -LiteralPath $PSCommandPath -Force + + # Stop logging + Write-Output 'Stop logging.' + Stop-Logging $logFile + + # Move log file + Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' + Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" + + # Report result and exit script + Write-Output "Update result: $result" + Write-Output 'Exiting.' + Exit +} + +# Pull current config to variable +Try { + Write-Output 'Pull current config to variable.' + $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + Invoke-Expression Update-Fail +} + +# Get currently downloaded version +Try { + Write-Output 'Getting currently downloaded version of the script.' + [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + Invoke-Expression Update-Fail +} + +# Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. +while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { + $timer++ + Start-Sleep -Seconds 1 + If ($timer -eq '60') { + Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' + } + Invoke-Expression Update-Fail +} + +# Pull latest version of script from GitHub +Try { + Write-Output 'Pull latest version of script from GitHub.' + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'download' + Invoke-Expression Update-Fail +} + +# Expand downloaded ZIP +Try { + Write-Output 'Expand downloaded ZIP.' + Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'unzip' + Invoke-Expression Update-Fail +} + +# Rename old version to keep as a backup while the update is in progress. +Try { + Write-Output 'Rename old version to make room for the new version.' + Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'rename_old' + Invoke-Expression Update-Fail +} + +# Rename extracted update +Try { + Write-Output 'Rename extracted update.' + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'rename_new' + Invoke-Expression Update-Fail +} + +# Pull configuration from new conf file +Try { + Write-Output 'Pull configuration from new conf file.' + $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail +} + +# Unblock script files +Write-Output 'Unblock script files.' +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -ErrorAction Continue +Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotification.ps1 -ErrorAction Continue + +# Populate conf.json with previous configuration +Try { + Write-Output 'Populate conf.json with previous configuration.' + $newConfig.webhook = $currentConfig.webhook + $newConfig.userid = $currentConfig.userid + if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { + $newConfig.mention_on_fail = $currentConfig.mention_on_fail + } + if ($currentConfig.debug_log -ne $newConfig.debug_log) { + $newConfig.debug_log = $currentConfig.debug_log + } + if ($currentConfig.auto_update -ne $newConfig.auto_update) { + $newConfig.auto_update = $currentConfig.auto_update + } + ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail +} + +# Get newly downloaded version +Try { + Write-Output 'Get newly downloaded version.' + [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail +} + +# Send notification +If ($newVersion -eq $LatestVersion) { + Invoke-Expression Update-Success +} +Else { + Invoke-Expression Update-Fail +} From 2ca376e92f27fabf134f7c88bc33ff2c033637ab Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 12 May 2020 19:25:39 +0100 Subject: [PATCH 032/175] Updated log file names. --- DiscordNotificationBootstrap.ps1 | 10 +++++++--- DiscordVeeamAlertSender.ps1 | 8 ++++++-- UpdateVeeamDiscordNotification.ps1 | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 3961624..5d97aa7 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -4,9 +4,13 @@ Import-Module "$PSScriptRoot\resources\logger.psm1" # Get the config from our config file. $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json -# Log if enabled in config. +# Start logging if logging is enabled in config if($config.debug_log) { - Start-Logging "$PSScriptRoot\log\debug.log" + ## Set log file name + $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) + $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" + ## Start logging to file + Start-Logging $logFile } # Add Veeam snap-in. @@ -33,5 +37,5 @@ Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArgume # Stop logging. if($config.debug_log) { - Stop-Logging "$PSScriptRoot\log\debug.log" + Stop-Logging $logFile } diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 0b29771..07d843c 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -12,7 +12,11 @@ $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFro # Log if enabled in config if($config.debug_log) { - Start-Logging "$PSScriptRoot\log\debug.log" + ## Set log file name + $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) + $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" + ## Start logging to file + Start-Logging $logFile } # Determine if an update is required. @@ -305,5 +309,5 @@ If ($currentVersion -lt $latestVersion -and $config.auto_update) { # Stop logging. if($config.debug_log) { - Stop-Logging "$PSScriptRoot\log\debug.log" + Stop-Logging $logFile } diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index aca5b1e..10d4d0f 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -9,7 +9,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" # Logging ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) -$logFile = "$PSScriptRoot\update_$date.log" +$logFile = "$PSScriptRoot\Log_Update-$date.log" ## Start logging to file Start-Logging $logFile From e568c4e58c86b99f6d101c3ac2769a5e7550c551 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 14 May 2020 00:23:40 +0100 Subject: [PATCH 033/175] Simplified determining latest version. --- DiscordVeeamAlertSender.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 07d843c..379f5b7 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -28,9 +28,7 @@ $currentVersion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw $latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing ## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"} so we need to extract tag_name. -$latestReleaseJson = $latestRelease.Content | ConvertFrom-Json -$latestVersion = $latestReleaseJson.tag_name - +$latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} ## Define version announcement phrases and get a random one for the version info in the footer of the report. $updateOlderArray = @( "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", From 4ff3df863b369e4f92ec6f9da6c90f501ffe6042 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 14 May 2020 01:09:50 +0100 Subject: [PATCH 034/175] Better comments in AlertSender. --- DiscordVeeamAlertSender.ps1 | 64 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 4d549a1..2cc19ed 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -1,16 +1,16 @@ -# Pull in variables that were set when the script was started by Veeam +# Pull in variables from the DiscordNotificationBootstrap script Param( [String]$jobName, [String]$id ) -# Import Functions +# Import functions Import-Module "$PSScriptRoot\resources\logger.psm1" -# Get the config from your config file +# Get config from your config file $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json -# Log if enabled in config +# Start logging if logging is enabled in config if($config.debug_log) { ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) @@ -19,17 +19,17 @@ if($config.debug_log) { Start-Logging $logFile } -# Determine if an update is required. +# Determine if an update is required ## Get currently downloaded version of this project. $currentVersion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw ## Get latest release from GitHub and use that to determine the latest version. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing - -## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"} so we need to extract tag_name. +## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"}, so we need to extract tag_name. $latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} -## Define version announcement phrases and get a random one for the version info in the footer of the report. + +## Define version announcement phrases. $updateOlderArray = @( "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", "Bloody hell you muppet, you need to update! Latest is $latestVersion. Check your update logs.", @@ -53,6 +53,7 @@ $updateNewerArray = @( ) ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. +## Picks a phrase at random from the list above for the version statement in the footer of the backup report. If ($currentVersion -lt $latestVersion) { $footerAddition = (Get-Random -InputObject $updateOlderArray -Count 1) } @@ -63,20 +64,20 @@ Elseif ($currentVersion -gt $latestVersion) { $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) } -# Add Veeam snap-in +# Add Veeam snap-in. Add-PSSnapin VeeamPSSnapin -# Get the session +# Get the backup session information. $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} -# Wait for the session to finish up -while ($session.IsCompleted -eq $false) { +# Wait for the backup session to finish. +While ($session.IsCompleted -eq $false) { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} } -# Gather session info +# Gather backup session info. [String]$status = $session.Result $jobName = $session.Name.ToString().Trim() $JobType = $session.JobTypeString.Trim() @@ -84,8 +85,8 @@ $JobType = $session.JobTypeString.Trim() [Float]$transferSize = $session.BackupStats.BackupSize [Float]$speed = $session.Info.Progress.AvgSpeed -# Determine whether to report the job and actual data sizes in B, KB, MB, GB, or TB, depending on completed size. Will fallback to B[ytes] if no match. -## Job size +# Determine whether to report the job and actual data sizes in B, KB, MB, GB, or TB, depending on completed size. Will fall back to B[ytes] if no match. +## Switch for job size. Switch ($jobSize) { ({$PSItem -lt 1KB}) { [String]$jobSizeRound = $jobSize @@ -121,7 +122,7 @@ Switch ($jobSize) { $jobSizeRound += ' B' } } -## Transfer size +## Switch for transfer size. Switch ($transferSize) { ({$PSItem -lt 1KB}) { [String]$transferSizeRound = $transferSize @@ -158,7 +159,8 @@ Switch ($transferSize) { } } -# Determine whether to report the job processing rate in B/s, KB/s, MB/s, or GB/s, depending on the figure. Will fallback to B[ytes] if no match. +# Determine whether to report the job processing speed in B/s, KB/s, MB/s, or GB/s, depending on the figure. Will fallback to B[ytes] if no match. +# Switch for speed. Switch ($speed) { ({$PSItem -lt 1KB}) { [String]$speedRound = $speed @@ -193,7 +195,7 @@ If ($speedRound -eq '0 B/s') { $speedRound = 'Unknown.' } -# Calculate job duration +# Calculate job duration. $TimeSpan = $session.Info.EndTime - $session.Info.CreationTime If ($TimeSpan.Days -ge '1') { $Duration = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds @@ -202,8 +204,8 @@ Else { $Duration = '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds } -# Switch on the session status -switch ($status) { +# Switch for the session status to decide the embed colour. +Switch ($status) { None {$colour = '16777215'} Warning {$colour = '16776960'} Success {$colour = '65280'} @@ -211,16 +213,16 @@ switch ($status) { Default {$colour = '16777215'} } -# Create embed and fields array +# Create embed and field arrays. [System.Collections.ArrayList]$embedArray = @() [System.Collections.ArrayList]$fieldArray = @() -# Thumbnail object +# Create thumbnail object. $thumbObject = [PSCustomObject]@{ url = $config.thumbnail } -# Field objects +# Create field objects. $backupSizeField = [PSCustomObject]@{ name = 'Backup size' value = [String]$jobSizeRound @@ -252,7 +254,7 @@ $speedField = [PSCustomObject]@{ inline = 'true' } -# Add field objects to the field array +# Add field objects to the field array. $fieldArray.Add($backupSizeField) | Out-Null $fieldArray.Add($transferSizeField) | Out-Null $fieldArray.Add($dedupField) | Out-Null @@ -260,13 +262,13 @@ $fieldArray.Add($compressField) | Out-Null $fieldArray.Add($durationField) | Out-Null $fieldArray.Add($speedField) | Out-Null -# Build footer object +# Build footer object. $footerObject = [PSCustomObject]@{ text = "tigattack's VeeamDiscordNotifications $currentVersion. $footerAddition" icon_url = 'https://avatars0.githubusercontent.com/u/10629864' } -# Embed object including field and thumbnail vars from above +# Build embed object. $embedObject = [PSCustomObject]@{ title = $jobName description = $status @@ -276,28 +278,28 @@ $embedObject = [PSCustomObject]@{ footer = $footerObject } -# Add embed object to the array created above +# Add embed object to the embed array. $embedArray.Add($embedObject) | Out-Null # Create payload -## Mention user if job failed +## Mention user on job failure if configured to do so. If ($config.mention_on_fail -and $status -eq 'Failed') { $payload = [PSCustomObject]@{ content = "<@!$($config.userid)> Job status $status" embeds = $embedArray } } -## Otherwise do not mention user +## Otherwise do not mention user. Else { $payload = [PSCustomObject]@{ embeds = $embedArray } } -# Send iiiit after converting to JSON +# Send iiiit. $request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' -# Trigger update on outdated version +# Trigger update if there's a newer version available. If ($currentVersion -lt $latestVersion -and $config.auto_update) { Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 From 9b0f0d160e59d3e5342eb7ebd52c04452e04e48f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 14 May 2020 20:22:54 +0100 Subject: [PATCH 035/175] Added .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..600d2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file From 298705289a46de508467966c0762fa195e6a8902 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 26 May 2020 22:15:11 +0100 Subject: [PATCH 036/175] Better comments. --- DiscordNotificationBootstrap.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 5d97aa7..7aba53f 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -4,7 +4,7 @@ Import-Module "$PSScriptRoot\resources\logger.psm1" # Get the config from our config file. $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json -# Start logging if logging is enabled in config +# Start logging if logging is enabled in config. if($config.debug_log) { ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) From 53408dfb7529151ebd6106928bf6973aabc371f2 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 18:36:31 +0100 Subject: [PATCH 037/175] Better job duration calculation. --- DiscordVeeamAlertSender.ps1 | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 2cc19ed..45d5b12 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -195,13 +195,29 @@ If ($speedRound -eq '0 B/s') { $speedRound = 'Unknown.' } -# Calculate job duration. -$TimeSpan = $session.Info.EndTime - $session.Info.CreationTime -If ($TimeSpan.Days -ge '1') { - $Duration = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds +# Calculate difference between job start and end time. +$duration = $session.Info.EndTime - $session.Info.CreationTime +# Switch for job duration. +Switch ($duration) { + ($_.Days -ge '1') { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds + break + } + ($_.Hours -ge '1') { + $durationFormatted = '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds + break + } + ($_.Minutes -ge '1') { + $durationFormatted = '{0}m {1}s' -f $TimeSpan.Minutes, $TimeSpan.Seconds + break + } + ($_.Seconds -ge '1') { + $durationFormatted = '{0}s' -f $TimeSpan.Seconds + break + } + Default { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds } -Else { - $Duration = '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds } # Switch for the session status to decide the embed colour. @@ -245,7 +261,7 @@ $compressField = [PSCustomObject]@{ } $durationField = [PSCustomObject]@{ name = 'Job Duration' - value = $duration + value = $durationFormatted inline = 'true' } $speedField = [PSCustomObject]@{ From 8adcc3860c5030d33e041d460e20e77c6bafb779 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 18:41:54 +0100 Subject: [PATCH 038/175] Added time started/completed as per #12. --- DiscordVeeamAlertSender.ps1 | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 45d5b12..704ce79 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -84,6 +84,8 @@ $JobType = $session.JobTypeString.Trim() [Float]$jobSize = $session.BackupStats.DataSize [Float]$transferSize = $session.BackupStats.BackupSize [Float]$speed = $session.Info.Progress.AvgSpeed +$jobEndTime = $session.Info.EndTime +$jobStartTime = $session.Info.CreationTime # Determine whether to report the job and actual data sizes in B, KB, MB, GB, or TB, depending on completed size. Will fall back to B[ytes] if no match. ## Switch for job size. @@ -196,7 +198,7 @@ If ($speedRound -eq '0 B/s') { } # Calculate difference between job start and end time. -$duration = $session.Info.EndTime - $session.Info.CreationTime +$duration = $jobEndTime - $jobStartTime # Switch for job duration. Switch ($duration) { ($_.Days -ge '1') { @@ -217,7 +219,7 @@ Switch ($duration) { } Default { $durationFormatted = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds -} + } } # Switch for the session status to decide the embed colour. @@ -269,6 +271,16 @@ $speedField = [PSCustomObject]@{ value = $speedRound inline = 'true' } +$startTimeField = [PSCustomObject]@{ + name = 'Time Started' + value = $jobStartTime + inline = 'true' +} +$endTimeField = [PSCustomObject]@{ + name = 'Time Completed' + value = $jobEndTime + inline = 'true' +} # Add field objects to the field array. $fieldArray.Add($backupSizeField) | Out-Null @@ -277,6 +289,8 @@ $fieldArray.Add($dedupField) | Out-Null $fieldArray.Add($compressField) | Out-Null $fieldArray.Add($durationField) | Out-Null $fieldArray.Add($speedField) | Out-Null +$fieldArray.Add($startTimeField) | Out-Null +$fieldArray.Add($endTimeField) | Out-Null # Build footer object. $footerObject = [PSCustomObject]@{ From dc03f983889d6ea148fa785ff9453cd28e84d323 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 20:29:11 +0100 Subject: [PATCH 039/175] Better config retrieval. --- DiscordVeeamAlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 704ce79..4244a4e 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -8,7 +8,7 @@ Param( Import-Module "$PSScriptRoot\resources\logger.psm1" # Get config from your config file -$config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json +$config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json # Start logging if logging is enabled in config if($config.debug_log) { From caa20f9c5b3fab096ffce3f1ac05799543727aa4 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:44:08 +0100 Subject: [PATCH 040/175] Move data size conversion to own module. --- DiscordVeeamAlertSender.ps1 | 110 ++--------------------------- resources/ConvertTo-ByteUnits.psm1 | 45 ++++++++++++ 2 files changed, 51 insertions(+), 104 deletions(-) create mode 100644 resources/ConvertTo-ByteUnits.psm1 diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 4244a4e..ebde55a 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -6,6 +6,7 @@ Param( # Import functions Import-Module "$PSScriptRoot\resources\logger.psm1" +Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnits.psm1" # Get config from your config file $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json @@ -87,111 +88,12 @@ $JobType = $session.JobTypeString.Trim() $jobEndTime = $session.Info.EndTime $jobStartTime = $session.Info.CreationTime -# Determine whether to report the job and actual data sizes in B, KB, MB, GB, or TB, depending on completed size. Will fall back to B[ytes] if no match. -## Switch for job size. -Switch ($jobSize) { - ({$PSItem -lt 1KB}) { - [String]$jobSizeRound = $jobSize - $jobSizeRound += ' B' - break - } - ({$PSItem -lt 1MB}) { - $jobSize = $jobSize / 1KB - [String]$jobSizeRound = [math]::Round($jobSize,2) - $jobSizeRound += ' KB' - break - } - ({$PSItem -lt 1GB}) { - $jobSize = $jobSize / 1MB - [String]$jobSizeRound = [math]::Round($jobSize,2) - $jobSizeRound += ' MB' - break - } - ({$PSItem -lt 1TB}) { - $jobSize = $jobSize / 1GB - [String]$jobSizeRound = [math]::Round($jobSize,2) - $jobSizeRound += ' GB' - break - } - ({$PSItem -ge 1TB}) { - $jobSize = $jobSize / 1TB - [String]$jobSizeRound = [math]::Round($jobSize,2) - $jobSizeRound += ' TB' - break - } - Default { - [String]$jobSizeRound = $jobSize - $jobSizeRound += ' B' - } -} -## Switch for transfer size. -Switch ($transferSize) { - ({$PSItem -lt 1KB}) { - [String]$transferSizeRound = $transferSize - $transferSizeRound += ' B' - break - } - ({$PSItem -lt 1MB}) { - $transferSize = $transferSize / 1KB - [String]$transferSizeRound = [math]::Round($transferSize,2) - $transferSizeRound += ' KB' - break - } - ({$PSItem -lt 1GB}) { - $transferSize = $transferSize / 1MB - [String]$transferSizeRound = [math]::Round($transferSize,2) - $transferSizeRound += ' MB' - break - } - ({$PSItem -lt 1TB}) { - $transferSize = $transferSize / 1GB - [String]$transferSizeRound = [math]::Round($transferSize,2) - $transferSizeRound += ' GB' - break - } - ({$PSItem -ge 1TB}) { - $transferSize = $transferSize / 1TB - [String]$transferSizeRound = [math]::Round($transferSize,2) - $transferSizeRound += ' TB' - break - } - Default { - [String]$transferSizeRound = $transferSize - $transferSizeRound += ' B' - } -} +# Convert bytes to rounded units. +$jobSizeRound = ConvertTo-ByteUnits -InputObject $jobSize +$transferSizeRound = ConvertTo-ByteUnits -InputObject $transferSize +## Convert speed in B/s to rounded units and append '/s' +$speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' -# Determine whether to report the job processing speed in B/s, KB/s, MB/s, or GB/s, depending on the figure. Will fallback to B[ytes] if no match. -# Switch for speed. -Switch ($speed) { - ({$PSItem -lt 1KB}) { - [String]$speedRound = $speed - $speedRound += ' B/s' - break - } - ({$PSItem -lt 1MB}) { - $speed = $speed / 1KB - [String]$speedRound = [math]::Round($speed,2) - $speedRound += ' KB/s' - break - } - ({$PSItem -lt 1GB}) { - $speed = $speed / 1MB - [String]$speedRound = [math]::Round($speed,2) - $speedRound += ' MB/s' - break - } - ({$PSItem -lt 1TB}) { - $speed = $speed / 1GB - [String]$speedRound = [math]::Round($speed,2) - $speedRound += ' GB/s' - break - } - Default { - [String]$speedRound = $speed - $speedRound += ' B/s' - } -} # Write "Unknown" processing speed if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { $speedRound = 'Unknown.' diff --git a/resources/ConvertTo-ByteUnits.psm1 b/resources/ConvertTo-ByteUnits.psm1 new file mode 100644 index 0000000..426af6d --- /dev/null +++ b/resources/ConvertTo-ByteUnits.psm1 @@ -0,0 +1,45 @@ +function ConvertTo-ByteUnits + { + [CmdletBinding()] + Param ( + [Parameter ( + Position = 0, + Mandatory)] + [int64] + $InputObject + ) + + begin {} + + process + { + $Sign = [math]::Sign($InputObject) + $InputObject = [math]::Abs($InputObject) + switch ($InputObject) + { + {$_ -ge 1TB } + {$Unit = 'TB'; break} + {$_ -ge 1GB } + {$Unit = 'GB'; break} + {$_ -ge 1MB } + {$Unit = 'MB'; break} + {$_ -ge 1KB } + {$Unit = 'KB'; break} + default + {$Unit = 'B'} + } + + if ($Unit -ne 'B') + { + '{0:N2} {1}' -f ($Sign * $InputObject / "1$Unit"), $Unit + } + else + { + '{0} {1}' -f ($Sign * $InputObject), $Unit + } + + } # end >> process + + end {} + + } # end >> function \ No newline at end of file From f401b912daa3e367796c3735a6ddaf7759f07359 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:49:39 +0100 Subject: [PATCH 041/175] Define and add fields to array at the same time rather than separately. --- DiscordVeeamAlertSender.ps1 | 59 +++++++++++++++---------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index ebde55a..669cc4b 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -133,66 +133,54 @@ Switch ($status) { Default {$colour = '16777215'} } -# Create embed and field arrays. -[System.Collections.ArrayList]$embedArray = @() -[System.Collections.ArrayList]$fieldArray = @() - # Create thumbnail object. $thumbObject = [PSCustomObject]@{ url = $config.thumbnail } -# Create field objects. -$backupSizeField = [PSCustomObject]@{ +# Create field objects and add to fieldArray. +$fieldArray = @( + [PSCustomObject]@{ name = 'Backup size' value = [String]$jobSizeRound inline = 'true' -} -$transferSizeField = [PSCustomObject]@{ + }, + [PSCustomObject]@{ name = 'Transferred Data' value = [String]$transferSizeRound inline = 'true' } -$dedupField = [PSCustomObject]@{ + [PSCustomObject]@{ name = 'Dedup Ratio' value = [String]$session.BackupStats.DedupRatio inline = 'true' } -$compressField = [PSCustomObject]@{ + [PSCustomObject]@{ name = 'Compression Ratio' value = [String]$session.BackupStats.CompressRatio inline = 'true' } -$durationField = [PSCustomObject]@{ - name = 'Job Duration' - value = $durationFormatted - inline = 'true' -} -$speedField = [PSCustomObject]@{ - name = 'Processing rate' - value = $speedRound + [PSCustomObject]@{ + name = 'Job Duration' + value = $durationFormatted + inline = 'true' + } + [PSCustomObject]@{ + name = 'Processing rate' + value = $speedRound inline = 'true' } -$startTimeField = [PSCustomObject]@{ + [PSCustomObject]@{ name = 'Time Started' - value = $jobStartTime + value = '{0}:{1}:{2}' -f $jobStartTime.Hour, $jobStartTime.Minute, $jobStartTime.Second inline = 'true' } -$endTimeField = [PSCustomObject]@{ + [PSCustomObject]@{ name = 'Time Completed' - value = $jobEndTime + value = '{0}:{1}:{2}' -f $jobEndTime.Hour, $jobEndTime.Minute, $jobEndTime.Second inline = 'true' } - -# Add field objects to the field array. -$fieldArray.Add($backupSizeField) | Out-Null -$fieldArray.Add($transferSizeField) | Out-Null -$fieldArray.Add($dedupField) | Out-Null -$fieldArray.Add($compressField) | Out-Null -$fieldArray.Add($durationField) | Out-Null -$fieldArray.Add($speedField) | Out-Null -$fieldArray.Add($startTimeField) | Out-Null -$fieldArray.Add($endTimeField) | Out-Null +) # Build footer object. $footerObject = [PSCustomObject]@{ @@ -201,7 +189,8 @@ $footerObject = [PSCustomObject]@{ } # Build embed object. -$embedObject = [PSCustomObject]@{ +$embedArray = @( + [PSCustomObject]@{ title = $jobName description = $status color = $colour @@ -209,9 +198,7 @@ $embedObject = [PSCustomObject]@{ fields = $fieldArray footer = $footerObject } - -# Add embed object to the embed array. -$embedArray.Add($embedObject) | Out-Null +) # Create payload ## Mention user on job failure if configured to do so. From 5cc53ba333f382cf2b06eaceea32c12e347cf8fe Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:50:46 +0100 Subject: [PATCH 042/175] Fix duration switch. --- DiscordVeeamAlertSender.ps1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 669cc4b..f15394f 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -103,24 +103,24 @@ If ($speedRound -eq '0 B/s') { $duration = $jobEndTime - $jobStartTime # Switch for job duration. Switch ($duration) { - ($_.Days -ge '1') { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds + {$_.Days -ge '1'} { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds break } - ($_.Hours -ge '1') { - $durationFormatted = '{0}h {1}m {2}s' -f $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds + {$_.Hours -ge '1'} { + $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds break } - ($_.Minutes -ge '1') { - $durationFormatted = '{0}m {1}s' -f $TimeSpan.Minutes, $TimeSpan.Seconds + {$_.Minutes -ge '1'} { + $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds break } - ($_.Seconds -ge '1') { - $durationFormatted = '{0}s' -f $TimeSpan.Seconds + {$_.Seconds -ge '1'} { + $durationFormatted = '{0}s' -f $_.Seconds break } Default { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $TimeSpan.Days, $TimeSpan.Hours, $TimeSpan.Minutes, $TimeSpan.Seconds + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds } } From dcd934d70901af7117d3e3a776e3d40acaa5c462 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:51:28 +0100 Subject: [PATCH 043/175] Better embed layout. --- DiscordVeeamAlertSender.ps1 | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index f15394f..af17cb8 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -141,45 +141,45 @@ $thumbObject = [PSCustomObject]@{ # Create field objects and add to fieldArray. $fieldArray = @( [PSCustomObject]@{ - name = 'Backup size' - value = [String]$jobSizeRound - inline = 'true' + name = 'Backup size' + value = [String]$jobSizeRound + inline = 'true' }, [PSCustomObject]@{ - name = 'Transferred Data' - value = [String]$transferSizeRound - inline = 'true' -} + name = 'Transferred Data' + value = [String]$transferSizeRound + inline = 'true' + } [PSCustomObject]@{ - name = 'Dedup Ratio' - value = [String]$session.BackupStats.DedupRatio - inline = 'true' -} + name = 'Dedup Ratio' + value = [String]$session.BackupStats.DedupRatio + inline = 'false' + } [PSCustomObject]@{ - name = 'Compression Ratio' - value = [String]$session.BackupStats.CompressRatio - inline = 'true' -} + name = 'Compression Ratio' + value = [String]$session.BackupStats.CompressRatio + inline = 'false' + } + [PSCustomObject]@{ + name = 'Processing rate' + value = $speedRound + inline = 'false' + } [PSCustomObject]@{ name = 'Job Duration' value = $durationFormatted inline = 'true' } [PSCustomObject]@{ - name = 'Processing rate' - value = $speedRound - inline = 'true' -} - [PSCustomObject]@{ - name = 'Time Started' + name = 'Time Started' value = '{0}:{1}:{2}' -f $jobStartTime.Hour, $jobStartTime.Minute, $jobStartTime.Second - inline = 'true' -} + inline = 'true' + } [PSCustomObject]@{ - name = 'Time Completed' + name = 'Time Completed' value = '{0}:{1}:{2}' -f $jobEndTime.Hour, $jobEndTime.Minute, $jobEndTime.Second - inline = 'true' -} + inline = 'true' + } ) # Build footer object. @@ -191,13 +191,13 @@ $footerObject = [PSCustomObject]@{ # Build embed object. $embedArray = @( [PSCustomObject]@{ - title = $jobName - description = $status - color = $colour - thumbnail = $thumbObject - fields = $fieldArray - footer = $footerObject -} + title = $jobName + description = $status + color = $colour + thumbnail = $thumbObject + fields = $fieldArray + footer = $footerObject + } ) # Create payload From 795024eb896cfcf40303eee56838a5e21a4e5d9c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Wed, 3 Jun 2020 23:57:21 +0100 Subject: [PATCH 044/175] Fix capitalisation in some embed field titles. --- DiscordVeeamAlertSender.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index af17cb8..8e2a448 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -141,7 +141,7 @@ $thumbObject = [PSCustomObject]@{ # Create field objects and add to fieldArray. $fieldArray = @( [PSCustomObject]@{ - name = 'Backup size' + name = 'Backup Size' value = [String]$jobSizeRound inline = 'true' }, @@ -161,7 +161,7 @@ $fieldArray = @( inline = 'false' } [PSCustomObject]@{ - name = 'Processing rate' + name = 'Processing Rate' value = $speedRound inline = 'false' } From 65cbb7fc993a2e5a8e772c38b6a1a6b2da730928 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 00:39:19 +0100 Subject: [PATCH 045/175] Replace 0 with 00 in job start and end times. --- DiscordVeeamAlertSender.ps1 | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 8e2a448..d68e11f 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -101,6 +101,38 @@ If ($speedRound -eq '0 B/s') { # Calculate difference between job start and end time. $duration = $jobEndTime - $jobStartTime + +# $jobEndTime and $jobStartTime are readonly. Create writeable object using their values so we can modify them. +$jobTimes = [PSCustomObject]@{ + StartHour = $jobStartTime.Hour + StartMinute = $jobStartTime.Minute + StartSecond = $jobStartTime.Second + EndHour = $jobEndTime.Hour + EndMinute = $jobEndTime.Minute + EndSecond = $jobEndTime.Second +} +# Change times from 0 to 00 if applicable +Switch ($jobTimes) { + {$_.StartHour -eq '0'} { + $jobTimes.StartHour = '00' + } + {$_.StartMinute -eq '0'} { + $jobTimes.StartMinute = '00' + } + {$_.StartSecond -eq '0'} { + $jobTimes.StartSecond = '00' + } + {$_.EndHour -eq '0'} { + $jobTimes.EndHour = '00' + } + {$_.EndMinute -eq '0'} { + $jobTimes.EndMinute = '00' + } + {$_.EndSecond -eq '0'} { + $jobTimes.EndSecond = '00' + } +} + # Switch for job duration. Switch ($duration) { {$_.Days -ge '1'} { @@ -172,12 +204,12 @@ $fieldArray = @( } [PSCustomObject]@{ name = 'Time Started' - value = '{0}:{1}:{2}' -f $jobStartTime.Hour, $jobStartTime.Minute, $jobStartTime.Second + value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond inline = 'true' } [PSCustomObject]@{ name = 'Time Completed' - value = '{0}:{1}:{2}' -f $jobEndTime.Hour, $jobEndTime.Minute, $jobEndTime.Second + value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond inline = 'true' } ) From 55401781874982fb6b15cb54d03bf80eeba49002 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 08:27:48 +0100 Subject: [PATCH 046/175] Added credits to README. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index a8c6b5f..4f30d1e 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,10 @@ Based upon [my fork](https://github.com/tigattack/VeeamSlackNotifications) of [T --- ## [Slack fork.](https://github.com/tigattack/VeeamSlackNotifications) ## [MS Teams fork.](https://github.com/tigattack/VeeamTeamsNotifications) + +## Credits +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for the [original project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is based. + +[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `UpdateVeeamDiscordNotifications.ps1` script. + +[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnits.psm1` function.](https://pastebin.com/srN5CKty) \ No newline at end of file From 7aa42177f34cd3a95b24406c5b4f90182de99149 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 08:55:25 +0100 Subject: [PATCH 047/175] Convert spaces to tabs. --- DiscordNotificationBootstrap.ps1 | 10 +- DiscordVeeamAlertSender.ps1 | 220 ++++++++--------- UpdateVeeamDiscordNotification.ps1 | 366 ++++++++++++++--------------- 3 files changed, 298 insertions(+), 298 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 7aba53f..47b5528 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -6,11 +6,11 @@ $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFro # Start logging if logging is enabled in config. if($config.debug_log) { - ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) - $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" - ## Start logging to file - Start-Logging $logFile + ## Set log file name + $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) + $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" + ## Start logging to file + Start-Logging $logFile } # Add Veeam snap-in. diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index d68e11f..49ae1a7 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -13,11 +13,11 @@ $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json # Start logging if logging is enabled in config if($config.debug_log) { - ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) - $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" - ## Start logging to file - Start-Logging $logFile + ## Set log file name + $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) + $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" + ## Start logging to file + Start-Logging $logFile } # Determine if an update is required @@ -32,37 +32,37 @@ $latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.ta ## Define version announcement phrases. $updateOlderArray = @( - "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", - "Bloody hell you muppet, you need to update! Latest is $latestVersion. Check your update logs.", - "Fuck me sideways, you're out of date! Latest is $latestVersion. Check your update logs.", - "Shitting heck lad, you need to update! Latest is $latestVersion. Check your update logs.", - "Christ almighty, you're out of date! Latest is $latestVersion. Check your update logs." + "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", + "Bloody hell you muppet, you need to update! Latest is $latestVersion. Check your update logs.", + "Fuck me sideways, you're out of date! Latest is $latestVersion. Check your update logs.", + "Shitting heck lad, you need to update! Latest is $latestVersion. Check your update logs.", + "Christ almighty, you're out of date! Latest is $latestVersion. Check your update logs." ) $updateCurrentArray = @( - "Nice work mate, you're up to date.", - "Good shit buddy, you're up to date.", - "Top stuff my dude, you're running the latest version.", - "Good job fam, you're all up to date.", - "Lovely stuff mate, you're running the latest version." + "Nice work mate, you're up to date.", + "Good shit buddy, you're up to date.", + "Top stuff my dude, you're running the latest version.", + "Good job fam, you're all up to date.", + "Lovely stuff mate, you're running the latest version." ) $updateNewerArray = @( - "Wewlad, check you out running a pre-release version, latest is $latestVersion!", - "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", - "You nutter, you're running a pre-release version! Latest is $latestVersion!", - "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!" - "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." + "Wewlad, check you out running a pre-release version, latest is $latestVersion!", + "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", + "You nutter, you're running a pre-release version! Latest is $latestVersion!", + "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!" + "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." ) ## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. ## Picks a phrase at random from the list above for the version statement in the footer of the backup report. If ($currentVersion -lt $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateOlderArray -Count 1) + $footerAddition = (Get-Random -InputObject $updateOlderArray -Count 1) } Elseif ($currentVersion -eq $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateCurrentArray -Count 1) + $footerAddition = (Get-Random -InputObject $updateCurrentArray -Count 1) } Elseif ($currentVersion -gt $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) + $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) } # Add Veeam snap-in. @@ -96,7 +96,7 @@ $speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' # Write "Unknown" processing speed if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { - $speedRound = 'Unknown.' + $speedRound = 'Unknown.' } # Calculate difference between job start and end time. @@ -105,8 +105,8 @@ $duration = $jobEndTime - $jobStartTime # $jobEndTime and $jobStartTime are readonly. Create writeable object using their values so we can modify them. $jobTimes = [PSCustomObject]@{ StartHour = $jobStartTime.Hour - StartMinute = $jobStartTime.Minute - StartSecond = $jobStartTime.Second + StartMinute = $jobStartTime.Minute + StartSecond = $jobStartTime.Second EndHour = $jobEndTime.Hour EndMinute = $jobEndTime.Minute EndSecond = $jobEndTime.Second @@ -135,34 +135,34 @@ Switch ($jobTimes) { # Switch for job duration. Switch ($duration) { - {$_.Days -ge '1'} { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Hours -ge '1'} { - $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Minutes -ge '1'} { - $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds - break - } - {$_.Seconds -ge '1'} { - $durationFormatted = '{0}s' -f $_.Seconds - break - } - Default { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - } + {$_.Days -ge '1'} { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Hours -ge '1'} { + $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Minutes -ge '1'} { + $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds + break + } + {$_.Seconds -ge '1'} { + $durationFormatted = '{0}s' -f $_.Seconds + break + } + Default { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + } } # Switch for the session status to decide the embed colour. Switch ($status) { - None {$colour = '16777215'} - Warning {$colour = '16776960'} - Success {$colour = '65280'} - Failed {$colour = '16711680'} - Default {$colour = '16777215'} + None {$colour = '16777215'} + Warning {$colour = '16776960'} + Success {$colour = '65280'} + Failed {$colour = '16711680'} + Default {$colour = '16777215'} } # Create thumbnail object. @@ -172,79 +172,79 @@ $thumbObject = [PSCustomObject]@{ # Create field objects and add to fieldArray. $fieldArray = @( - [PSCustomObject]@{ - name = 'Backup Size' - value = [String]$jobSizeRound - inline = 'true' - }, - [PSCustomObject]@{ - name = 'Transferred Data' - value = [String]$transferSizeRound - inline = 'true' - } - [PSCustomObject]@{ - name = 'Dedup Ratio' - value = [String]$session.BackupStats.DedupRatio - inline = 'false' - } - [PSCustomObject]@{ - name = 'Compression Ratio' - value = [String]$session.BackupStats.CompressRatio - inline = 'false' - } - [PSCustomObject]@{ - name = 'Processing Rate' - value = $speedRound - inline = 'false' - } - [PSCustomObject]@{ - name = 'Job Duration' - value = $durationFormatted - inline = 'true' - } - [PSCustomObject]@{ - name = 'Time Started' - value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond - inline = 'true' - } - [PSCustomObject]@{ - name = 'Time Completed' - value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond - inline = 'true' - } + [PSCustomObject]@{ + name = 'Backup Size' + value = [String]$jobSizeRound + inline = 'true' + }, + [PSCustomObject]@{ + name = 'Transferred Data' + value = [String]$transferSizeRound + inline = 'true' + } + [PSCustomObject]@{ + name = 'Dedup Ratio' + value = [String]$session.BackupStats.DedupRatio + inline = 'false' + } + [PSCustomObject]@{ + name = 'Compression Ratio' + value = [String]$session.BackupStats.CompressRatio + inline = 'false' + } + [PSCustomObject]@{ + name = 'Processing Rate' + value = $speedRound + inline = 'false' + } + [PSCustomObject]@{ + name = 'Job Duration' + value = $durationFormatted + inline = 'true' + } + [PSCustomObject]@{ + name = 'Time Started' + value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond + inline = 'true' + } + [PSCustomObject]@{ + name = 'Time Completed' + value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond + inline = 'true' + } ) # Build footer object. $footerObject = [PSCustomObject]@{ text = "tigattack's VeeamDiscordNotifications $currentVersion. $footerAddition" - icon_url = 'https://avatars0.githubusercontent.com/u/10629864' + icon_url = 'https://avatars0.githubusercontent.com/u/10629864' } # Build embed object. $embedArray = @( - [PSCustomObject]@{ - title = $jobName - description = $status - color = $colour - thumbnail = $thumbObject - fields = $fieldArray - footer = $footerObject - } + [PSCustomObject]@{ + title = $jobName + description = $status + color = $colour + thumbnail = $thumbObject + fields = $fieldArray + footer = $footerObject + } ) # Create payload ## Mention user on job failure if configured to do so. If ($config.mention_on_fail -and $status -eq 'Failed') { - $payload = [PSCustomObject]@{ - content = "<@!$($config.userid)> Job status $status" - embeds = $embedArray - } + $payload = [PSCustomObject]@{ + content = "<@!$($config.userid)> Job status $status" + embeds = $embedArray + } } ## Otherwise do not mention user. Else { - $payload = [PSCustomObject]@{ - embeds = $embedArray - } + $payload = [PSCustomObject]@{ + embeds = $embedArray + } } # Send iiiit. @@ -252,10 +252,10 @@ $request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Js # Trigger update if there's a newer version available. If ($currentVersion -lt $latestVersion -and $config.auto_update) { - Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 - Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 - $powershellArguments = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" - Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden + Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 + Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 + $powershellArguments = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" + Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden } # Stop logging. diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 10d4d0f..9da92e6 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -1,6 +1,6 @@ # Pull version from script trigger Param ( - [string]$LatestVersion + [string]$LatestVersion ) # Import functions @@ -19,154 +19,154 @@ $ErrorActionPreference = 'Stop' # Notification function function Update-Notification { - Write-Output 'Building notification.' - # Create embed and fields array - [System.Collections.ArrayList]$embedArray = @() - [System.Collections.ArrayList]$fieldArray = @() - # Thumbnail object - $thumbObject = [PSCustomObject]@{ - url = $currentConfig.thumbnail - } - # Field objects - $resultField = [PSCustomObject]@{ - name = 'Update Result' - value = $result - inline = 'false' - } - $newVersionField = [PSCustomObject]@{ - name = 'New version' - value = $newVersion - inline = 'false' - } - $oldVersionField = [PSCustomObject]@{ - name = 'Old version' - value = $oldVersion - inline = 'false' - } - # Add field objects to the field array - $fieldArray.Add($oldVersionField) | Out-Null - $fieldArray.Add($newVersionField) | Out-Null - $fieldArray.Add($resultField) | Out-Null - # Send error if exist - If ($null -ne $errorVar) { - $errorField = [PSCustomObject]@{ - name = 'Update Error' - value = $errorVar - inline = 'false' - } - $fieldArray.Add($errorField) | Out-Null - } - # Embed object including field and thumbnail vars from above - $embedObject = [PSCustomObject]@{ - title = 'Update' - color = '1267393' - thumbnail = $thumbObject - fields = $fieldArray - } - # Add embed object to the array created above - $embedArray.Add($embedObject) | Out-Null - # Build payload - $payload = [PSCustomObject]@{ - embeds = $embedArray - } - Write-Output 'Sending notification.' - # Send iiit - Try { - Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' - } - Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Warning 'Update notification failed to send to Discord.' - Write-Output "$errorVar" - } + Write-Output 'Building notification.' + # Create embed and fields array + [System.Collections.ArrayList]$embedArray = @() + [System.Collections.ArrayList]$fieldArray = @() + # Thumbnail object + $thumbObject = [PSCustomObject]@{ + url = $currentConfig.thumbnail + } + # Field objects + $resultField = [PSCustomObject]@{ + name = 'Update Result' + value = $result + inline = 'false' + } + $newVersionField = [PSCustomObject]@{ + name = 'New version' + value = $newVersion + inline = 'false' + } + $oldVersionField = [PSCustomObject]@{ + name = 'Old version' + value = $oldVersion + inline = 'false' + } + # Add field objects to the field array + $fieldArray.Add($oldVersionField) | Out-Null + $fieldArray.Add($newVersionField) | Out-Null + $fieldArray.Add($resultField) | Out-Null + # Send error if exist + If ($null -ne $errorVar) { + $errorField = [PSCustomObject]@{ + name = 'Update Error' + value = $errorVar + inline = 'false' + } + $fieldArray.Add($errorField) | Out-Null + } + # Embed object including field and thumbnail vars from above + $embedObject = [PSCustomObject]@{ + title = 'Update' + color = '1267393' + thumbnail = $thumbObject + fields = $fieldArray + } + # Add embed object to the array created above + $embedArray.Add($embedObject) | Out-Null + # Build payload + $payload = [PSCustomObject]@{ + embeds = $embedArray + } + Write-Output 'Sending notification.' + # Send iiit + Try { + Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + } + Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Warning 'Update notification failed to send to Discord.' + Write-Output "$errorVar" + } } # Success function function Update-Success { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' - # Set result var for notification and script output - $result = 'Success!' + # Set result var for notification and script output + $result = 'Success!' - # Copy logs directory from copy of previously installed version to new install - Write-Output 'Copying logs from old version to new version.' - Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force + # Copy logs directory from copy of previously installed version to new install + Write-Output 'Copying logs from old version to new version.' + Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force - # Remove copy of previously installed version - Write-Output 'Removing old version.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force + # Remove copy of previously installed version + Write-Output 'Removing old version.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script + # Trigger the Update-Notification function and then End-Script function. + Invoke-Expression Update-Notification + Invoke-Expression End-Script } # Failure function function Update-Fail { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' - # Set result var for notification and script output - $result = 'Failure!' + # Set result var for notification and script output + $result = 'Failure!' - # Take action based on the stage at which the error occured - Switch ($fail) { - download { - Write-Warning 'Failed to download update.' - } - unzip { - Write-Warning 'Failed to unzip update. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - } - rename_old { - Write-Warning 'Failed to rename old version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force - } - rename_new { - Write-Warning 'Failed to rename new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - after_rename_new { - Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - } + # Take action based on the stage at which the error occured + Switch ($fail) { + download { + Write-Warning 'Failed to download update.' + } + unzip { + Write-Warning 'Failed to unzip update. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + } + rename_old { + Write-Warning 'Failed to rename old version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + } + rename_new { + Write-Warning 'Failed to rename new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + after_rename_new { + Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + } - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script + # Trigger the Update-Notification function and then End-Script function. + Invoke-Expression Update-Notification + Invoke-Expression End-Script } # End of script function function Stop-Script { - # Clean up. - Write-Output 'Remove downloaded ZIP.' - If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { - Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" - } - Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' - Remove-Item -LiteralPath $PSCommandPath -Force + # Clean up. + Write-Output 'Remove downloaded ZIP.' + If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { + Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" + } + Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' + Remove-Item -LiteralPath $PSCommandPath -Force - # Stop logging - Write-Output 'Stop logging.' - Stop-Logging $logFile + # Stop logging + Write-Output 'Stop logging.' + Stop-Logging $logFile - # Move log file - Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' - Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" + # Move log file + Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' + Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" - # Report result and exit script - Write-Output "Update result: $result" - Write-Output 'Exiting.' - Exit + # Report result and exit script + Write-Output "Update result: $result" + Write-Output 'Exiting.' + Exit } # Pull current config to variable @@ -175,43 +175,43 @@ Try { $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + Invoke-Expression Update-Fail } # Get currently downloaded version Try { - Write-Output 'Getting currently downloaded version of the script.' - [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw + Write-Output 'Getting currently downloaded version of the script.' + [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + Invoke-Expression Update-Fail } # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { - $timer++ - Start-Sleep -Seconds 1 - If ($timer -eq '60') { - Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' - } - Invoke-Expression Update-Fail + $timer++ + Start-Sleep -Seconds 1 + If ($timer -eq '60') { + Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' + } + Invoke-Expression Update-Fail } # Pull latest version of script from GitHub Try { Write-Output 'Pull latest version of script from GitHub.' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip + Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'download' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'download' + Invoke-Expression Update-Fail } # Expand downloaded ZIP @@ -220,10 +220,10 @@ Try { Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'unzip' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'unzip' + Invoke-Expression Update-Fail } # Rename old version to keep as a backup while the update is in progress. @@ -232,10 +232,10 @@ Try { Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'rename_old' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'rename_old' + Invoke-Expression Update-Fail } # Rename extracted update @@ -244,10 +244,10 @@ Try { Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'rename_new' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'rename_new' + Invoke-Expression Update-Fail } # Pull configuration from new conf file @@ -256,10 +256,10 @@ Try { $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Unblock script files @@ -273,23 +273,23 @@ Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotificat Try { Write-Output 'Populate conf.json with previous configuration.' $newConfig.webhook = $currentConfig.webhook - $newConfig.userid = $currentConfig.userid - if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { - $newConfig.mention_on_fail = $currentConfig.mention_on_fail - } - if ($currentConfig.debug_log -ne $newConfig.debug_log) { - $newConfig.debug_log = $currentConfig.debug_log - } - if ($currentConfig.auto_update -ne $newConfig.auto_update) { - $newConfig.auto_update = $currentConfig.auto_update - } - ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" + $newConfig.userid = $currentConfig.userid + if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { + $newConfig.mention_on_fail = $currentConfig.mention_on_fail + } + if ($currentConfig.debug_log -ne $newConfig.debug_log) { + $newConfig.debug_log = $currentConfig.debug_log + } + if ($currentConfig.auto_update -ne $newConfig.auto_update) { + $newConfig.auto_update = $currentConfig.auto_update + } + ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Get newly downloaded version @@ -298,16 +298,16 @@ Try { [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" - $fail = 'after_rename_new' - Invoke-Expression Update-Fail + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'after_rename_new' + Invoke-Expression Update-Fail } # Send notification If ($newVersion -eq $LatestVersion) { - Invoke-Expression Update-Success + Invoke-Expression Update-Success } Else { - Invoke-Expression Update-Fail + Invoke-Expression Update-Fail } From e23d7e52478898848dac38d6d3975c6cb7e54652 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 08:56:04 +0100 Subject: [PATCH 048/175] Newline at end of file. --- .gitignore | 2 +- resources/ConvertTo-ByteUnits.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 600d2d3..722d5e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -.vscode \ No newline at end of file +.vscode diff --git a/resources/ConvertTo-ByteUnits.psm1 b/resources/ConvertTo-ByteUnits.psm1 index 426af6d..886e0bd 100644 --- a/resources/ConvertTo-ByteUnits.psm1 +++ b/resources/ConvertTo-ByteUnits.psm1 @@ -42,4 +42,4 @@ function ConvertTo-ByteUnits end {} - } # end >> function \ No newline at end of file + } # end >> function From fea7aa64bf1a5995b2178bb37d70d640590713d5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 09:30:54 +0100 Subject: [PATCH 049/175] Update gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 722d5e7..6ecc562 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vscode +notes.txt \ No newline at end of file From a253f6785ad6a8ca0b698521324b399d73daae07 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 10:42:54 +0100 Subject: [PATCH 050/175] Newline at end of file. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6ecc562..92578a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .vscode -notes.txt \ No newline at end of file +notes.txt From e0c934cc1497b030b14148903b0ecdea902b8f03 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 10:51:35 +0100 Subject: [PATCH 051/175] Fix variable unavailable between update functions. --- UpdateVeeamDiscordNotification.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 9da92e6..99145ee 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -88,7 +88,7 @@ function Update-Success { $ErrorActionPreference = 'Continue' # Set result var for notification and script output - $result = 'Success!' + $script:result = 'Success!' # Copy logs directory from copy of previously installed version to new install Write-Output 'Copying logs from old version to new version.' @@ -110,7 +110,7 @@ function Update-Fail { $ErrorActionPreference = 'Continue' # Set result var for notification and script output - $result = 'Failure!' + $script:result = 'Failure!' # Take action based on the stage at which the error occured Switch ($fail) { From 1a777f1422d136bda0291658205e7bf77b35fb74 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 10:51:50 +0100 Subject: [PATCH 052/175] Convert spaces to tabs in logger function. --- resources/logger.psm1 | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/logger.psm1 b/resources/logger.psm1 index 87da4f0..34eb780 100644 --- a/resources/logger.psm1 +++ b/resources/logger.psm1 @@ -1,18 +1,18 @@ # This function logs messages with a type tag function Write-LogMessage($tag, $message) { - Write-Host "[$tag] $message" + Write-Host "[$tag] $message" } # This function handles Logging function Start-Logging($path) { - try { - Start-Transcript -path $path -force -append - Write-LogMessage -Tag 'Info' -Message "Transcript is being logged to $path" - } catch [Exception] { - Write-LogMessage -Tag 'Info' -Message "Transcript is already being logged to $path" - } + try { + Start-Transcript -path $path -force -append + Write-LogMessage -Tag 'Info' -Message "Transcript is being logged to $path" + } catch [Exception] { + Write-LogMessage -Tag 'Info' -Message "Transcript is already being logged to $path" + } } function Stop-Logging { - Write-LogMessage -Tag 'Info' -Message "Stopping transcript logging." - Stop-Transcript + Write-LogMessage -Tag 'Info' -Message "Stopping transcript logging." + Stop-Transcript } From d6f24baadd3eccf9825d6a9d1f68ba9718019101 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 10:52:06 +0100 Subject: [PATCH 053/175] Write error if message fails to send to Discord. --- DiscordVeeamAlertSender.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 49ae1a7..eccd196 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -250,6 +250,12 @@ Else { # Send iiiit. $request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' +# Write error if message fails to send to Discord. +If ($request.Length -gt '0') { + Write-LogMessage -Tag 'Error' -Message 'Failed to send message to Discord. Response below.' + Write-LogMessage -Tag 'Error' -Message "$request" +} + # Trigger update if there's a newer version available. If ($currentVersion -lt $latestVersion -and $config.auto_update) { Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 From 9629ef4d87817cba7463b7374c3919b3fad20811 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 4 Jun 2020 20:52:22 +0100 Subject: [PATCH 054/175] Update README.md Better words. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f30d1e..53558ad 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Based upon [my fork](https://github.com/tigattack/VeeamSlackNotifications) of [T ## [MS Teams fork.](https://github.com/tigattack/VeeamTeamsNotifications) ## Credits -[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for the [original project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is based. +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](TheSageColleges/VeeamSlackNotifications) on which this is based. [dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `UpdateVeeamDiscordNotifications.ps1` script. -[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnits.psm1` function.](https://pastebin.com/srN5CKty) \ No newline at end of file +[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnits.psm1` function.](https://pastebin.com/srN5CKty) From 2353d8584f0baef7a9a5ae1ed9da85d27c17b08c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jun 2020 15:57:20 +0100 Subject: [PATCH 055/175] chore(.gitignore): Update gitignore. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 92578a1..6415d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .vscode -notes.txt +notes.md From 8933f931e634cdd50495b0b1406db6fcc5d1ee3b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jun 2020 15:57:29 +0100 Subject: [PATCH 056/175] style(DiscordNotificationBootstrap.ps1): Better comments. --- DiscordNotificationBootstrap.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 47b5528..b5786f8 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -16,7 +16,7 @@ if($config.debug_log) { # Add Veeam snap-in. Add-PSSnapin VeeamPSSnapin -# Get the Veeam job from parent process. +# Get the command line used to start the Veeam session. $parentPID = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() $parentCmd = (Get-WmiObject Win32_Process -Filter "processid='$parentPID'").CommandLine $job = Get-VBRJob | Where-Object{$parentCmd -like "*"+$_.Id.ToString()+"*"} @@ -32,7 +32,7 @@ $jobName = '"' + $session.OrigJobName.ToString().Trim() + '"' $powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $id" # Start a new new script in a new process with some of the information gathered here. -# This allows Veeam to finish the current session so that we can gather information from the completed job. +# This allows Veeam to finish the current session faster and allows us gather information from the completed job. Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden # Stop logging. From 42fc99c57bfbc5b52ec1fd2b40c129aa0b24ce15 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jun 2020 15:59:52 +0100 Subject: [PATCH 057/175] feat(Get-VBRSessionInfo.psm1): Function to detect job type Add function to detect if job is VM backup or Agent backup. Required for #9. --- resources/Get-VBRSessionInfo.psm1 | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 resources/Get-VBRSessionInfo.psm1 diff --git a/resources/Get-VBRSessionInfo.psm1 b/resources/Get-VBRSessionInfo.psm1 new file mode 100644 index 0000000..6079390 --- /dev/null +++ b/resources/Get-VBRSessionInfo.psm1 @@ -0,0 +1,31 @@ +Function Get-VBRSessionInfo { + param ( + [Parameter(Mandatory=$true)]$sessionId, + [Parameter(Mandatory=$true)]$jobType + ) + + If (($null -ne $sessionId) -and ($null -ne $jobType)) { + Switch ($jobType) { + {$_ -eq 'VM'} { + # Get the session details. + $script:session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $sessionId} + # Get the session's name. + $script:jobName = $session.OrigJobName + } + {$_ -eq 'Agent'} { + # Copy the job's name to it's own variable. + $script:jobName = $job.Info.Name + # Get the Veeam session. + $script:session = Get-VBRComputerBackupJobSession -Id $sessionId + } + } + } + + Elseif ($null -eq $sessionId) { + Write-Host 'Error: $sessionId is null.' + } + + Elseif ($null -eq $jobType) { + Write-Host 'Error: $jobType is null.' + } +} From 04ef4541dd6312ccd49bf582a1d9e12907c96525 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 5 Jun 2020 16:09:00 +0100 Subject: [PATCH 058/175] docs(readme.md): Fixed link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53558ad..c076b75 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Based upon [my fork](https://github.com/tigattack/VeeamSlackNotifications) of [T ## [MS Teams fork.](https://github.com/tigattack/VeeamTeamsNotifications) ## Credits -[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](TheSageColleges/VeeamSlackNotifications) on which this is based. +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is based. [dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `UpdateVeeamDiscordNotifications.ps1` script. From 175e2d1a7a618871ba5de7b61990324d8efab078 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 7 Jun 2020 12:06:14 +0100 Subject: [PATCH 059/175] fix(DiscordVeeamAlertSender.ps1): Fix time strings Time strings were not being formatted correctly. Now using a much improved method. --- DiscordVeeamAlertSender.ps1 | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index eccd196..2dc0cd9 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -102,35 +102,14 @@ If ($speedRound -eq '0 B/s') { # Calculate difference between job start and end time. $duration = $jobEndTime - $jobStartTime -# $jobEndTime and $jobStartTime are readonly. Create writeable object using their values so we can modify them. +# $jobEndTime and $jobStartTime are readonly. Create writeable object using their values, prepending 0 to single-digit values. $jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour - StartMinute = $jobStartTime.Minute - StartSecond = $jobStartTime.Second - EndHour = $jobEndTime.Hour - EndMinute = $jobEndTime.Minute - EndSecond = $jobEndTime.Second -} -# Change times from 0 to 00 if applicable -Switch ($jobTimes) { - {$_.StartHour -eq '0'} { - $jobTimes.StartHour = '00' - } - {$_.StartMinute -eq '0'} { - $jobTimes.StartMinute = '00' - } - {$_.StartSecond -eq '0'} { - $jobTimes.StartSecond = '00' - } - {$_.EndHour -eq '0'} { - $jobTimes.EndHour = '00' - } - {$_.EndMinute -eq '0'} { - $jobTimes.EndMinute = '00' - } - {$_.EndSecond -eq '0'} { - $jobTimes.EndSecond = '00' - } + StartHour = $jobStartTime.Hour.ToString("00") + StartMinute = $jobStartTime.Minute.ToString("00") + StartSecond = $jobStartTime.Second.ToString("00") + EndHour = $jobEndTime.Hour.ToString("00") + EndMinute = $jobEndTime.Minute.ToString("00") + EndSecond = $jobEndTime.Second.ToString("00") } # Switch for job duration. From 1747f6a0595d663d21134f3bb49a2413f2d8844e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 24 Jul 2020 14:50:56 +0100 Subject: [PATCH 060/175] feat(Bootstrap): New job identification method Use different mechanism to identify the job ID and session ID, and identify the job from those two. --- DiscordNotificationBootstrap.ps1 | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index b5786f8..7471545 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -1,5 +1,6 @@ # Import Functions. Import-Module "$PSScriptRoot\resources\logger.psm1" +Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" # Get the config from our config file. $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json @@ -19,17 +20,28 @@ Add-PSSnapin VeeamPSSnapin # Get the command line used to start the Veeam session. $parentPID = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() $parentCmd = (Get-WmiObject Win32_Process -Filter "processid='$parentPID'").CommandLine -$job = Get-VBRJob | Where-Object{$parentCmd -like "*"+$_.Id.ToString()+"*"} -# Get the Veeam session. -$session = Get-VBRBackupSession | Where-Object{($_.OrigJobName -eq $job.Name) -and ($parentCmd -like "*"+$_.Id.ToString()+"*")} +# Get the Veeam job and session IDs +$jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] +$sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] +# Get the Veeam job details and hide warnings to mute the warning regarding depreciation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative. +$job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} + +# Get the job time +Switch ($job.JobType) { + {$_ -eq 'Backup'} { + $jobType = 'VM' + } + {$_ -eq 'EpAgentBackup'} { + $jobType = 'Agent' + } +} -# Store the job's name and ID. -$id = '"' + $session.Id.ToString().ToString().Trim() + '"' -$jobName = '"' + $session.OrigJobName.ToString().Trim() + '"' +# Get the session information and name. +Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType # Build argument string for the alert sender. -$powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $id" +$powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. From 1f4ac2ac7e88947a6c546ceafcd10145e410e7f5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 24 Jul 2020 14:57:38 +0100 Subject: [PATCH 061/175] feat(AlertSender): New methods to gather information --- DiscordVeeamAlertSender.ps1 | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 2dc0cd9..89ec98c 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -1,12 +1,14 @@ # Pull in variables from the DiscordNotificationBootstrap script Param( [String]$jobName, - [String]$id + [String]$id, + [String]$jobType ) # Import functions Import-Module "$PSScriptRoot\resources\logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnits.psm1" +Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" # Get config from your config file $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json @@ -69,22 +71,19 @@ Elseif ($currentVersion -gt $latestVersion) { Add-PSSnapin VeeamPSSnapin # Get the backup session information. -$session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} +$session = Get-VBRSessionInfo -SessionID $id -JobType $jobType # Wait for the backup session to finish. While ($session.IsCompleted -eq $false) { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 - $session = Get-VBRBackupSession | Where-Object{($_.OrigjobName -eq $jobName) -and ($id -eq $_.Id.ToString())} + Get-VBRSessionInfo -SessionID $id -JobType $jobType } -# Gather backup session info. +# Gather generic session info [String]$status = $session.Result -$jobName = $session.Name.ToString().Trim() -$JobType = $session.JobTypeString.Trim() -[Float]$jobSize = $session.BackupStats.DataSize -[Float]$transferSize = $session.BackupStats.BackupSize -[Float]$speed = $session.Info.Progress.AvgSpeed +$jobEndTime = $session.Info.EndTime +$jobStartTime = $session.Info.CreationTime $jobEndTime = $session.Info.EndTime $jobStartTime = $session.Info.CreationTime From d598c4fcc7ecc8d44f6c6ebcbf9256f52f26695b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 24 Jul 2020 14:59:39 +0100 Subject: [PATCH 062/175] feat(AlertSender): Build embed for agent or VM job --- DiscordVeeamAlertSender.ps1 | 123 +++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 52 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 89ec98c..7fbf44e 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -74,7 +74,7 @@ Add-PSSnapin VeeamPSSnapin $session = Get-VBRSessionInfo -SessionID $id -JobType $jobType # Wait for the backup session to finish. -While ($session.IsCompleted -eq $false) { +While ($session.State -ne 'Stopped') { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 Get-VBRSessionInfo -SessionID $id -JobType $jobType @@ -84,18 +84,53 @@ While ($session.IsCompleted -eq $false) { [String]$status = $session.Result $jobEndTime = $session.Info.EndTime $jobStartTime = $session.Info.CreationTime -$jobEndTime = $session.Info.EndTime -$jobStartTime = $session.Info.CreationTime -# Convert bytes to rounded units. -$jobSizeRound = ConvertTo-ByteUnits -InputObject $jobSize -$transferSizeRound = ConvertTo-ByteUnits -InputObject $transferSize -## Convert speed in B/s to rounded units and append '/s' -$speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' +if ($jobType -eq 'VM') { + # Gatherr session info for VM backup. + [String]$status = $session.Result + [Float]$jobSize = $session.BackupStats.DataSize + [Float]$transferSize = $session.BackupStats.BackupSize + [Float]$speed = $session.Info.Progress.AvgSpeed + + # Convert bytes to rounded units. + $jobSizeRound = ConvertTo-ByteUnits -InputObject $jobSize + $transferSizeRound = ConvertTo-ByteUnits -InputObject $transferSize + ## Convert speed from B/s to rounded units and append '/s' + $speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' -# Write "Unknown" processing speed if 0B/s to avoid confusion. -If ($speedRound -eq '0 B/s') { - $speedRound = 'Unknown.' + # Write "Unknown" processing speed if 0B/s to avoid confusion. + If ($speedRound -eq '0 B/s') { + $speedRound = 'Unknown.' + } + + # Create field objects and add to fieldArray. + $fieldArray = @( + [PSCustomObject]@{ + name = 'Backup Size' + value = [String]$jobSizeRound + inline = 'true' + }, + [PSCustomObject]@{ + name = 'Transferred Data' + value = [String]$transferSizeRound + inline = 'true' + } + [PSCustomObject]@{ + name = 'Dedup Ratio' + value = [String]$session.BackupStats.DedupRatio + inline = 'false' + } + [PSCustomObject]@{ + name = 'Compression Ratio' + value = [String]$session.BackupStats.CompressRatio + inline = 'false' + } + [PSCustomObject]@{ + name = 'Processing Rate' + value = $speedRound + inline = 'false' + } + ) } # Calculate difference between job start and end time. @@ -134,47 +169,7 @@ Switch ($duration) { } } -# Switch for the session status to decide the embed colour. -Switch ($status) { - None {$colour = '16777215'} - Warning {$colour = '16776960'} - Success {$colour = '65280'} - Failed {$colour = '16711680'} - Default {$colour = '16777215'} -} - -# Create thumbnail object. -$thumbObject = [PSCustomObject]@{ - url = $config.thumbnail -} - -# Create field objects and add to fieldArray. -$fieldArray = @( - [PSCustomObject]@{ - name = 'Backup Size' - value = [String]$jobSizeRound - inline = 'true' - }, - [PSCustomObject]@{ - name = 'Transferred Data' - value = [String]$transferSizeRound - inline = 'true' - } - [PSCustomObject]@{ - name = 'Dedup Ratio' - value = [String]$session.BackupStats.DedupRatio - inline = 'false' - } - [PSCustomObject]@{ - name = 'Compression Ratio' - value = [String]$session.BackupStats.CompressRatio - inline = 'false' - } - [PSCustomObject]@{ - name = 'Processing Rate' - value = $speedRound - inline = 'false' - } +$fieldArray += @( [PSCustomObject]@{ name = 'Job Duration' value = $durationFormatted @@ -192,6 +187,30 @@ $fieldArray = @( } ) +If ($jobType -eq 'Agent') { + $fieldArray += @( + [PSCustomObject]@{ + name = 'Notice' + value = "Veeam's PowerShell snappin provides very little information about agent backups, so unfortunately this is all that can be provided for the time being." + inline = 'false' + } + ) +} + +# Switch for the session status to decide the embed colour. +Switch ($status) { + None {$colour = '16777215'} + Warning {$colour = '16776960'} + Success {$colour = '65280'} + Failed {$colour = '16711680'} + Default {$colour = '16777215'} +} + +# Create thumbnail object. +$thumbObject = [PSCustomObject]@{ + url = $config.thumbnail +} + # Build footer object. $footerObject = [PSCustomObject]@{ text = "tigattack's VeeamDiscordNotifications $currentVersion. $footerAddition" From c6c2f9387a8fc5ecce24c5b27ff0609d2f2f75b9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:36:17 +0000 Subject: [PATCH 063/175] style(Update): Capitalise --- UpdateVeeamDiscordNotification.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index 99145ee..deab853 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -88,7 +88,7 @@ function Update-Success { $ErrorActionPreference = 'Continue' # Set result var for notification and script output - $script:result = 'Success!' + $Script:result = 'Success!' # Copy logs directory from copy of previously installed version to new install Write-Output 'Copying logs from old version to new version.' @@ -110,7 +110,7 @@ function Update-Fail { $ErrorActionPreference = 'Continue' # Set result var for notification and script output - $script:result = 'Failure!' + $Script:result = 'Failure!' # Take action based on the stage at which the error occured Switch ($fail) { From d3df80d8052744135c9e99acbb4927404c69bd6b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 16 Mar 2021 17:36:33 +0000 Subject: [PATCH 064/175] chore(gitignore): Add ExampleData dir. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6415d4c..ff4a8a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode notes.md +ExampleData \ No newline at end of file From 847d926ea1d2b2739af78eec512a465dfee0a8f7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:33:00 +0100 Subject: [PATCH 065/175] feat(various): Bring up to date --- DiscordNotificationBootstrap.ps1 | 10 +++++----- DiscordVeeamAlertSender.ps1 | 11 ++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 7471545..261fd44 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -14,12 +14,12 @@ if($config.debug_log) { Start-Logging $logFile } -# Add Veeam snap-in. -Add-PSSnapin VeeamPSSnapin +# Import Veeam module. +Import-Module Veeam.Backup.PowerShell # Get the command line used to start the Veeam session. -$parentPID = (Get-WmiObject Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() -$parentCmd = (Get-WmiObject Win32_Process -Filter "processid='$parentPID'").CommandLine +$parentPID = (Get-CimInstance Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() +$parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").CommandLine # Get the Veeam job and session IDs $jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] @@ -49,5 +49,5 @@ Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArgume # Stop logging. if($config.debug_log) { - Stop-Logging $logFile + Stop-Logging } diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 7fbf44e..86218a9 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -67,8 +67,8 @@ Elseif ($currentVersion -gt $latestVersion) { $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) } -# Add Veeam snap-in. -Add-PSSnapin VeeamPSSnapin +# Import Veeam module +Import-Module Veeam.Backup.PowerShell # Get the backup session information. $session = Get-VBRSessionInfo -SessionID $id -JobType $jobType @@ -229,6 +229,11 @@ $embedArray = @( } ) +# Decide whether to mention user +If (($config.mention_on_fail -and $Status -eq 'Failed') -or ($config.mention_on_warning -and $Status -eq 'Warning')) { + $mention = $true +} + # Create payload ## Mention user on job failure if configured to do so. If ($config.mention_on_fail -and $status -eq 'Failed') { @@ -263,5 +268,5 @@ If ($currentVersion -lt $latestVersion -and $config.auto_update) { # Stop logging. if($config.debug_log) { - Stop-Logging $logFile + Stop-Logging } From 21bfa8f56f3bc18b61a6a0aaa90362346d88001e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:34:22 +0100 Subject: [PATCH 066/175] feat(updater): Bring up to date --- UpdateVeeamDiscordNotification.ps1 | 298 ++++++++++++++++------------- 1 file changed, 163 insertions(+), 135 deletions(-) diff --git a/UpdateVeeamDiscordNotification.ps1 b/UpdateVeeamDiscordNotification.ps1 index deab853..0b516b3 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/UpdateVeeamDiscordNotification.ps1 @@ -19,154 +19,182 @@ $ErrorActionPreference = 'Stop' # Notification function function Update-Notification { - Write-Output 'Building notification.' - # Create embed and fields array - [System.Collections.ArrayList]$embedArray = @() - [System.Collections.ArrayList]$fieldArray = @() - # Thumbnail object - $thumbObject = [PSCustomObject]@{ - url = $currentConfig.thumbnail - } - # Field objects - $resultField = [PSCustomObject]@{ - name = 'Update Result' - value = $result - inline = 'false' - } - $newVersionField = [PSCustomObject]@{ - name = 'New version' - value = $newVersion - inline = 'false' - } - $oldVersionField = [PSCustomObject]@{ - name = 'Old version' - value = $oldVersion - inline = 'false' - } - # Add field objects to the field array - $fieldArray.Add($oldVersionField) | Out-Null - $fieldArray.Add($newVersionField) | Out-Null - $fieldArray.Add($resultField) | Out-Null - # Send error if exist - If ($null -ne $errorVar) { - $errorField = [PSCustomObject]@{ - name = 'Update Error' - value = $errorVar + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param () + If ($PSCmdlet.ShouldProcess('Discord', 'Send update notification')) { + Write-Output 'Building notification.' + # Create embed and fields array + [System.Collections.ArrayList]$embedArray = @() + [System.Collections.ArrayList]$fieldArray = @() + # Thumbnail object + $thumbObject = [PSCustomObject]@{ + url = $currentConfig.thumbnail + } + # Field objects + $resultField = [PSCustomObject]@{ + name = 'Update Result' + value = $result inline = 'false' } - $fieldArray.Add($errorField) | Out-Null - } - # Embed object including field and thumbnail vars from above - $embedObject = [PSCustomObject]@{ - title = 'Update' - color = '1267393' - thumbnail = $thumbObject - fields = $fieldArray - } - # Add embed object to the array created above - $embedArray.Add($embedObject) | Out-Null - # Build payload - $payload = [PSCustomObject]@{ - embeds = $embedArray - } - Write-Output 'Sending notification.' - # Send iiit - Try { - Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' - } - Catch { - $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Warning 'Update notification failed to send to Discord.' - Write-Output "$errorVar" + $newVersionField = [PSCustomObject]@{ + name = 'New version' + value = $newVersion + inline = 'false' + } + $oldVersionField = [PSCustomObject]@{ + name = 'Old version' + value = $oldVersion + inline = 'false' + } + # Add field objects to the field array + $fieldArray.Add($oldVersionField) | Out-Null + $fieldArray.Add($newVersionField) | Out-Null + $fieldArray.Add($resultField) | Out-Null + # Send error if exist + If ($null -ne $errorVar) { + $errorField = [PSCustomObject]@{ + name = 'Update Error' + value = $errorVar + inline = 'false' + } + $fieldArray.Add($errorField) | Out-Null + } + # Embed object including field and thumbnail vars from above + $embedObject = [PSCustomObject]@{ + title = 'Update' + color = '1267393' + thumbnail = $thumbObject + fields = $fieldArray + } + # Add embed object to the array created above + $embedArray.Add($embedObject) | Out-Null + # Build payload + $payload = [PSCustomObject]@{ + embeds = $embedArray + } + Write-Output 'Sending notification.' + # Send iiit + Try { + Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + } + Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Warning 'Update notification failed to send to Discord.' + Write-Output "$errorVar" + } } } # Success function function Update-Success { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param () + If ($PSCmdlet.ShouldProcess('Updater', 'Update success process')) { + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' - # Set result var for notification and script output - $Script:result = 'Success!' + # Set result var for notification and script output + $script:result = 'Success!' - # Copy logs directory from copy of previously installed version to new install - Write-Output 'Copying logs from old version to new version.' - Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force + # Copy logs directory from copy of previously installed version to new install + Write-Output 'Copying logs from old version to new version.' + Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force - # Remove copy of previously installed version - Write-Output 'Removing old version.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force + # Remove copy of previously installed version + Write-Output 'Removing old version.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script + # Trigger the Update-Notification function and then End-Script function. + Update-Notification + End-Script + } } # Failure function function Update-Fail { - # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' - $ErrorActionPreference = 'Continue' + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param () + If ($PSCmdlet.ShouldProcess('Updater', 'Update failure process')) { + # Set error action preference so that errors while ending the script don't end the script prematurely. + Write-Output 'Set error action preference.' + $ErrorActionPreference = 'Continue' - # Set result var for notification and script output - $Script:result = 'Failure!' + # Set result var for notification and script output + $script:result = 'Failure!' - # Take action based on the stage at which the error occured - Switch ($fail) { - download { - Write-Warning 'Failed to download update.' - } - unzip { - Write-Warning 'Failed to unzip update. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - } - rename_old { - Write-Warning 'Failed to rename old version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + # Take action based on the stage at which the error occured + Switch ($fail) { + download { + Write-Warning 'Failed to download update.' + } + unzip { + Write-Warning 'Failed to unzip update. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + } + rename_old { + Write-Warning 'Failed to rename old version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + } + rename_new { + Write-Warning 'Failed to rename new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } + after_rename_new { + Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force + Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force + Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications + } } - rename_new { - Write-Warning 'Failed to rename new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - after_rename_new { - Write-Warning 'Failed after renaming new version. Cleaning up and reverting.' - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -Force - Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications -Recurse -Force - Rename-Item $PSScriptRoot\VeeamDiscordNotifications-old $PSScriptRoot\VeeamDiscordNotifications - } - } - # Trigger the Update-Notification function and then End-Script function. - Invoke-Expression Update-Notification - Invoke-Expression End-Script + # Trigger the Update-Notification function and then End-Script function. + Update-Notification + End-Script + } } # End of script function function Stop-Script { - # Clean up. - Write-Output 'Remove downloaded ZIP.' - If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { - Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" - } - Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' - Remove-Item -LiteralPath $PSCommandPath -Force + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param () + If ($PSCmdlet.ShouldProcess('Updater', 'Cleanup & stop')) { + # Clean up. + Write-Output 'Remove downloaded ZIP.' + If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { + Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" + } + Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' + Remove-Item -LiteralPath $PSCommandPath -Force - # Stop logging - Write-Output 'Stop logging.' - Stop-Logging $logFile + # Stop logging + Write-Output 'Stop logging.' + Stop-Logging $logFile - # Move log file - Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' - Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" + # Move log file + Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' + Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" - # Report result and exit script - Write-Output "Update result: $result" - Write-Output 'Exiting.' - Exit + # Report result and exit script + Write-Output "Update result: $result" + Write-Output 'Exiting.' + Exit + } } # Pull current config to variable @@ -177,7 +205,7 @@ Try { Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" - Invoke-Expression Update-Fail + Update-Fail } # Get currently downloaded version @@ -188,17 +216,17 @@ Try { Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" - Invoke-Expression Update-Fail + Update-Fail } # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. -while (Get-WmiObject win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { +while (Get-CimInstance win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { $timer++ Start-Sleep -Seconds 1 If ($timer -eq '60') { Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' } - Invoke-Expression Update-Fail + Update-Fail } # Pull latest version of script from GitHub @@ -211,7 +239,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'download' - Invoke-Expression Update-Fail + Update-Fail } # Expand downloaded ZIP @@ -223,7 +251,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'unzip' - Invoke-Expression Update-Fail + Update-Fail } # Rename old version to keep as a backup while the update is in progress. @@ -235,7 +263,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'rename_old' - Invoke-Expression Update-Fail + Update-Fail } # Rename extracted update @@ -247,7 +275,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'rename_new' - Invoke-Expression Update-Fail + Update-Fail } # Pull configuration from new conf file @@ -259,7 +287,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'after_rename_new' - Invoke-Expression Update-Fail + Update-Fail } # Unblock script files @@ -289,7 +317,7 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'after_rename_new' - Invoke-Expression Update-Fail + Update-Fail } # Get newly downloaded version @@ -301,13 +329,13 @@ Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Output "$errorVar" $fail = 'after_rename_new' - Invoke-Expression Update-Fail + Update-Fail } # Send notification If ($newVersion -eq $LatestVersion) { - Invoke-Expression Update-Success + Update-Success } Else { - Invoke-Expression Update-Fail + Update-Fail } From 79c30276b6827e6fd25f8df63363d92a571bb247 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 6 Jul 2021 20:45:58 +0100 Subject: [PATCH 067/175] feat(various): Bring up to date --- .github/ISSUE_TEMPLATE/bug_report.md | 26 ++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++ .github/scripts/pssa-settings.psd1 | 29 +++++++++++++ .github/scripts/pssa.ps1 | 28 ++++++++++++ .github/workflows/main.yml | 16 +++++++ .gitignore | 2 +- config/conf.json | 1 + resources/ConvertTo-ByteUnits.psm1 | 2 +- resources/logger.psm1 | 52 +++++++++++++++++------ 9 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/scripts/pssa-settings.psd1 create mode 100644 .github/scripts/pssa.ps1 create mode 100644 .github/workflows/main.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..57fa26d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,26 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Relevant part of `log/debug.log`** +_If you don't see a `debug.log`, set `debug_log` to true in `config/conf.json` and reproduce the issue._ + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..29c9f0a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEAT] " +labels: enhancement +assignees: tigattack + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +- Definition of done +A short, concise definition of how this feature would look or work when done. +- Stretch +Any additional functionality that isn't required by would be nice. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/scripts/pssa-settings.psd1 b/.github/scripts/pssa-settings.psd1 new file mode 100644 index 0000000..c36fb36 --- /dev/null +++ b/.github/scripts/pssa-settings.psd1 @@ -0,0 +1,29 @@ +# Settings for PSScriptAnalyzer invocation. +@{ + Rules = @{ + PSUseCompatibleCommands = @{ + Enable = $true + # PowerShell platforms we want to check compatibility with + TargetProfiles = @( + 'win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows Server 2016 + 'win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows Server 2019 + 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows 10 + 'win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2016 + 'win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2019 + 'win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows 10 + 'win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Windows Server 2016 + 'win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Server 2019 + 'win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core' # PowerShell 7.0 on Windows 10 + ) + } + PSUseCompatibleSyntax = @{ + Enable = $true + # PowerShell versions we want to check compatibility with + TargetVersions = @( + '5.1', + '6.2', + '7.1' + ) + } + } +} diff --git a/.github/scripts/pssa.ps1 b/.github/scripts/pssa.ps1 new file mode 100644 index 0000000..182c447 --- /dev/null +++ b/.github/scripts/pssa.ps1 @@ -0,0 +1,28 @@ +# Install PSSA module +Set-PSRepository PSGallery -InstallationPolicy Trusted +Install-Module PSScriptAnalyzer -ErrorAction Stop + +# Run PSSA +Invoke-ScriptAnalyzer -Path * -Recurse -Settings ./.github/scripts/pssa-settings.psd1 -OutVariable issues | Out-Null + +# Get results and separate types +$errors = $issues.Where({$_.Severity -eq 'Error' -or $_.Severity -eq 'ParseError'}) +$warnings = $issues.Where({$_.Severity -eq 'Warning'}) +$infos = $issues.Where({$_.Severity -eq 'Information'}) + +# Report results to GitHub Actions +Foreach ($i in $errors) { + Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" +} +Foreach ($i in $warnings) { + Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" +} +Foreach ($i in $infos) { + Write-Output "::debug file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" +} + +Write-Output "There were $($errors.Count) errors, $($warnings.Count) warnings, and $($infos.Count) infos in total." + +If ($errors) { + exit 1 +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..b3f338f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,16 @@ +--- +name: CI +on: + push: + pull_request: + workflow_dispatch: + +jobs: + lint: + name: Run PSScriptAnalyzer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Lint with PSScriptAnalyzer + shell: pwsh + run: ./.github/scripts/pssa.ps1 diff --git a/.gitignore b/.gitignore index ff4a8a8..eca9c0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vscode notes.md -ExampleData \ No newline at end of file +ExampleData diff --git a/config/conf.json b/config/conf.json index 76bf25a..af73b52 100644 --- a/config/conf.json +++ b/config/conf.json @@ -3,6 +3,7 @@ "thumbnail": "https://raw.githubusercontent.com/tigattack/VeeamDiscordNotifications/master/asset/thumb01.png", "userid": "123456789", "mention_on_fail": false, + "mention_on_warning": false, "debug_log": false, "auto_update": true } diff --git a/resources/ConvertTo-ByteUnits.psm1 b/resources/ConvertTo-ByteUnits.psm1 index 886e0bd..c22792c 100644 --- a/resources/ConvertTo-ByteUnits.psm1 +++ b/resources/ConvertTo-ByteUnits.psm1 @@ -1,4 +1,4 @@ -function ConvertTo-ByteUnits +function ConvertTo-ByteUnit { [CmdletBinding()] Param ( diff --git a/resources/logger.psm1 b/resources/logger.psm1 index 34eb780..bf45571 100644 --- a/resources/logger.psm1 +++ b/resources/logger.psm1 @@ -1,18 +1,46 @@ # This function logs messages with a type tag -function Write-LogMessage($tag, $message) { - Write-Host "[$tag] $message" +Function Write-LogMessage { + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param ( + $tag, + $message + ) + If ($PSCmdlet.ShouldProcess('Output stream', 'Write log message')) { + Write-Output "[$tag] $message" + } } -# This function handles Logging -function Start-Logging($path) { - try { - Start-Transcript -path $path -force -append - Write-LogMessage -Tag 'Info' -Message "Transcript is being logged to $path" - } catch [Exception] { - Write-LogMessage -Tag 'Info' -Message "Transcript is already being logged to $path" +# These functions handles Logging +Function Start-Logging { + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param( + [Parameter(Mandatory)] + $path + ) + If ($PSCmdlet.ShouldProcess($path, 'Start-Transcript')) { + Try { + Start-Transcript -Path $path -Force -Append + Write-LogMessage -Tag 'Info' -Message "Transcript is being logged to $path" + } + Catch [Exception] { + Write-LogMessage -Tag 'Info' -Message "Transcript is already being logged to $path" + } } } -function Stop-Logging { - Write-LogMessage -Tag 'Info' -Message "Stopping transcript logging." - Stop-Transcript +Function Stop-Logging { + [CmdletBinding( + SupportsShouldProcess, + ConfirmImpact = 'Low' + )] + Param() + If ($PSCmdlet.ShouldProcess('log file', 'Stop-Transcript')) { + Write-LogMessage -Tag 'Info' -Message "Stopping transcript logging." + Stop-Transcript + } } From db9d3bced06ea5116958948802a2700451c7cf10 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:13:48 +0100 Subject: [PATCH 068/175] feat(alertsender): Split update checking to module --- DiscordVeeamAlertSender.ps1 | 46 ++++-------------------- resources/Get-UpdateInformation.psm1 | 54 ++++++++++++++++++++++++++++ resources/VersionPhrases.json | 23 ++++++++++++ 3 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 resources/Get-UpdateInformation.psm1 create mode 100644 resources/VersionPhrases.json diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 86218a9..010bc4a 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -9,6 +9,7 @@ Param( Import-Module "$PSScriptRoot\resources\logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnits.psm1" Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" +Import-Module "$PSScriptRoot\resources\Get-UpdateInformation.psm1" # Get config from your config file $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json @@ -23,49 +24,14 @@ if($config.debug_log) { } # Determine if an update is required -## Get currently downloaded version of this project. -$currentVersion = Get-Content "$PSScriptRoot\resources\version.txt" -Raw +$updateStatus = Get-UpdateStatus -## Get latest release from GitHub and use that to determine the latest version. -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing -## Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"}, so we need to extract tag_name. -$latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} -## Define version announcement phrases. -$updateOlderArray = @( - "Jesus mate, you're out of date! Latest is $latestVersion. Check your update logs.", - "Bloody hell you muppet, you need to update! Latest is $latestVersion. Check your update logs.", - "Fuck me sideways, you're out of date! Latest is $latestVersion. Check your update logs.", - "Shitting heck lad, you need to update! Latest is $latestVersion. Check your update logs.", - "Christ almighty, you're out of date! Latest is $latestVersion. Check your update logs." -) -$updateCurrentArray = @( - "Nice work mate, you're up to date.", - "Good shit buddy, you're up to date.", - "Top stuff my dude, you're running the latest version.", - "Good job fam, you're all up to date.", - "Lovely stuff mate, you're running the latest version." -) -$updateNewerArray = @( - "Wewlad, check you out running a pre-release version, latest is $latestVersion!", - "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", - "You nutter, you're running a pre-release version! Latest is $latestVersion!", - "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!" - "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." -) +# Define static output objects. + +## Get and define update status message. +$footerAddition = Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion -## Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. -## Picks a phrase at random from the list above for the version statement in the footer of the backup report. -If ($currentVersion -lt $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateOlderArray -Count 1) -} -Elseif ($currentVersion -eq $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateCurrentArray -Count 1) -} -Elseif ($currentVersion -gt $latestVersion) { - $footerAddition = (Get-Random -InputObject $updateNewerArray -Count 1) -} # Import Veeam module Import-Module Veeam.Backup.PowerShell diff --git a/resources/Get-UpdateInformation.psm1 b/resources/Get-UpdateInformation.psm1 new file mode 100644 index 0000000..0c97132 --- /dev/null +++ b/resources/Get-UpdateInformation.psm1 @@ -0,0 +1,54 @@ +function Get-UpdateStatus { + + process { + # Get currently downloaded version of this project. + $currentVersion = Get-Content "$PSScriptRoot\version.txt" -Raw + + # Get latest release from GitHub. + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + $latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing + + # Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"}, so we need to extract tag_name. + $latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} + + # Create PSObject to return. + New-Object PSObject -Property @{ + CurrentVersion = $currentVersion + LatestVersion = $latestVersion + } + } +} + +function Get-UpdateMessage { + Param ( + [Parameter(Mandatory)] + $CurrentVersion, + [Parameter(Mandatory)] + $LatestVersion + ) + + process { + + # Get version announcement phrases. + $phrases = Get-Content -Raw "$PSScriptRoot\VersionPhrases.json" | ConvertFrom-Json + + # Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. + # Picks a phrase at random from the list above for the version statement in the footer of the backup report. + Switch ($currentVersion) { + {$_ -lt $latestVersion} { + $updateMessage = (Get-Random -InputObject $phrases.older -Count 1) + } + + {$_ -eq $latestVersion} { + $updateMessage = (Get-Random -InputObject $phrases.current -Count 1) + } + + {$_ -gt $latestVersion} { + $updateMessage = (Get-Random -InputObject $phrases.newer -Count 1) + } + } + + # Return update message. + $updateMessage + } +} diff --git a/resources/VersionPhrases.json b/resources/VersionPhrases.json new file mode 100644 index 0000000..35d4c2e --- /dev/null +++ b/resources/VersionPhrases.json @@ -0,0 +1,23 @@ +{ + "older": [ + "Jesus mate, you're out of date! Latest is $latestVersion.", + "Bloody hell you muppet, you need to update! Latest is $latestVersion.", + "Fuck me sideways, you're out of date! Latest is $latestVersion.", + "Shitting heck lad, you need to update! Latest is $latestVersion.", + "Christ almighty, you're out of date! Latest is $latestVersion." + ], + "current": [ + "Nice work mate, you're up to date.", + "Good shit buddy, you're up to date.", + "Top stuff my dude, you're running the latest version.", + "Good job fam, you're all up to date.", + "Lovely stuff mate, you're running the latest version." + ], + "newer": [ + "Wewlad, check you out running a pre-release version, latest is $latestVersion!", + "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", + "You nutter, you're running a pre-release version! Latest is $latestVersion!", + "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!", + "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." + ] +} From 039b83c249c3e59bb3875292f5c48522c2179619 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:18:26 +0100 Subject: [PATCH 069/175] feat(vbrsessioninfo): Output PSObject Rather than using script-scope variables. --- DiscordNotificationBootstrap.ps1 | 3 ++- DiscordVeeamAlertSender.ps1 | 9 ++++----- resources/Get-VBRSessionInfo.psm1 | 27 +++++++++++++++++++++------ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 261fd44..0b5af0e 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -38,7 +38,8 @@ Switch ($job.JobType) { } # Get the session information and name. -Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType +$sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType +$jobName = $sessionInfo.JobName # Build argument string for the alert sender. $powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 010bc4a..7800154 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -33,17 +33,16 @@ $updateStatus = Get-UpdateStatus $footerAddition = Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion -# Import Veeam module -Import-Module Veeam.Backup.PowerShell +# Job info preparation -# Get the backup session information. -$session = Get-VBRSessionInfo -SessionID $id -JobType $jobType +## Get the backup session information. +$session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session # Wait for the backup session to finish. While ($session.State -ne 'Stopped') { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 - Get-VBRSessionInfo -SessionID $id -JobType $jobType + $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session } # Gather generic session info diff --git a/resources/Get-VBRSessionInfo.psm1 b/resources/Get-VBRSessionInfo.psm1 index 5cdf835..7290e04 100644 --- a/resources/Get-VBRSessionInfo.psm1 +++ b/resources/Get-VBRSessionInfo.psm1 @@ -4,21 +4,36 @@ Function Get-VBRSessionInfo { [Parameter(Mandatory=$true)]$jobType ) + # Import VBR module + Import-Module Veeam.Backup.PowerShell + If (($null -ne $sessionId) -and ($null -ne $jobType)) { + + # Switch on job type. Switch ($jobType) { {$_ -eq 'VM'} { + # Get the session details. - $script:session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $sessionId} - # Get the session's name. - $script:jobName = $session.OrigJobName + $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $sessionId} + + # Get the job's name from the session details. + $jobName = $session.OrigJobName } + {$_ -eq 'Agent'} { + # Get the session details. + $session = Get-VBRComputerBackupJobSession -Id $sessionId + # Copy the job's name to it's own variable. - $script:jobName = $job.Info.Name - # Get the Veeam session. - $script:session = Get-VBRComputerBackupJobSession -Id $sessionId + $jobName = $job.Info.Name } } + + # Create PSObject to return. + New-Object PSObject -Property @{ + Session = $session + JobName = $jobName + } } Elseif ($null -eq $sessionId) { From 2813d1015a8ad54e329d39133a00998b4152fd15 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:24:41 +0100 Subject: [PATCH 070/175] style(alertsender): Improve comments, restructure --- DiscordVeeamAlertSender.ps1 | 125 ++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 7800154..8a4e87b 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -1,4 +1,4 @@ -# Pull in variables from the DiscordNotificationBootstrap script +# Define parameters Param( [String]$jobName, [String]$id, @@ -11,7 +11,7 @@ Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnits.psm1" Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" Import-Module "$PSScriptRoot\resources\Get-UpdateInformation.psm1" -# Get config from your config file +# Get config from file $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json # Start logging if logging is enabled in config @@ -23,6 +23,7 @@ if($config.debug_log) { Start-Logging $logFile } + # Determine if an update is required $updateStatus = Get-UpdateStatus @@ -32,24 +33,76 @@ $updateStatus = Get-UpdateStatus ## Get and define update status message. $footerAddition = Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion +## Define thumbnail object. +$thumbObject = [PSCustomObject]@{ + url = $config.thumbnail +} + +## Define footer object. +$footerObject = [PSCustomObject]@{ + text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion). $footerAddition" + icon_url = 'https://avatars0.githubusercontent.com/u/10629864' +} + # Job info preparation ## Get the backup session information. $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session -# Wait for the backup session to finish. +## Wait for the backup session to finish. While ($session.State -ne 'Stopped') { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' Start-Sleep -m 200 $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session } -# Gather generic session info +## Gather generic session info [String]$status = $session.Result $jobEndTime = $session.Info.EndTime $jobStartTime = $session.Info.CreationTime +## Create writeable object using their values, prepending 0 to single-digit values. +## Necessary because $jobEndTime and $jobStartTime are readonly. +$jobTimes = [PSCustomObject]@{ + StartHour = $jobStartTime.Hour.ToString("00") + StartMinute = $jobStartTime.Minute.ToString("00") + StartSecond = $jobStartTime.Second.ToString("00") + EndHour = $jobEndTime.Hour.ToString("00") + EndMinute = $jobEndTime.Minute.ToString("00") + EndSecond = $jobEndTime.Second.ToString("00") +} + +## Calculate difference between job start and end time. +$duration = $jobEndTime - $jobStartTime + +## Switch for job duration; define pretty output. +Switch ($duration) { + {$_.Days -ge '1'} { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Hours -ge '1'} { + $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Minutes -ge '1'} { + $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds + break + } + {$_.Seconds -ge '1'} { + $durationFormatted = '{0}s' -f $_.Seconds + break + } + Default { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + } +} + + +# Define session statistics for the report. + +## If VM backup, gather and include session info if ($jobType -eq 'VM') { # Gatherr session info for VM backup. [String]$status = $session.Result @@ -63,12 +116,12 @@ if ($jobType -eq 'VM') { ## Convert speed from B/s to rounded units and append '/s' $speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' - # Write "Unknown" processing speed if 0B/s to avoid confusion. + # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { $speedRound = 'Unknown.' } - # Create field objects and add to fieldArray. + # Add session information to fieldArray. $fieldArray = @( [PSCustomObject]@{ name = 'Backup Size' @@ -98,42 +151,7 @@ if ($jobType -eq 'VM') { ) } -# Calculate difference between job start and end time. -$duration = $jobEndTime - $jobStartTime - -# $jobEndTime and $jobStartTime are readonly. Create writeable object using their values, prepending 0 to single-digit values. -$jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour.ToString("00") - StartMinute = $jobStartTime.Minute.ToString("00") - StartSecond = $jobStartTime.Second.ToString("00") - EndHour = $jobEndTime.Hour.ToString("00") - EndMinute = $jobEndTime.Minute.ToString("00") - EndSecond = $jobEndTime.Second.ToString("00") -} - -# Switch for job duration. -Switch ($duration) { - {$_.Days -ge '1'} { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Hours -ge '1'} { - $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Minutes -ge '1'} { - $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds - break - } - {$_.Seconds -ge '1'} { - $durationFormatted = '{0}s' -f $_.Seconds - break - } - Default { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - } -} - +## Add job times to fieldArray. $fieldArray += @( [PSCustomObject]@{ name = 'Job Duration' @@ -152,6 +170,7 @@ $fieldArray += @( } ) +# If agent backup, add notice to fieldArray. If ($jobType -eq 'Agent') { $fieldArray += @( [PSCustomObject]@{ @@ -171,17 +190,6 @@ Switch ($status) { Default {$colour = '16777215'} } -# Create thumbnail object. -$thumbObject = [PSCustomObject]@{ - url = $config.thumbnail -} - -# Build footer object. -$footerObject = [PSCustomObject]@{ - text = "tigattack's VeeamDiscordNotifications $currentVersion. $footerAddition" - icon_url = 'https://avatars0.githubusercontent.com/u/10629864' -} - # Build embed object. $embedArray = @( [PSCustomObject]@{ @@ -199,7 +207,7 @@ If (($config.mention_on_fail -and $Status -eq 'Failed') -or ($config.mention_on_ $mention = $true } -# Create payload +# Create payload object. ## Mention user on job failure if configured to do so. If ($config.mention_on_fail -and $status -eq 'Failed') { $payload = [PSCustomObject]@{ @@ -207,6 +215,7 @@ If ($config.mention_on_fail -and $status -eq 'Failed') { embeds = $embedArray } } + ## Otherwise do not mention user. Else { $payload = [PSCustomObject]@{ @@ -214,6 +223,7 @@ Else { } } + # Send iiiit. $request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' @@ -223,12 +233,15 @@ If ($request.Length -gt '0') { Write-LogMessage -Tag 'Error' -Message "$request" } + # Trigger update if there's a newer version available. If ($currentVersion -lt $latestVersion -and $config.auto_update) { Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 - $powershellArguments = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" - Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden + + # Run update script. + $updateArgs = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" + Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden } # Stop logging. From 4ac6ad3f7e2f167ec93648a268d6f37d2b0f1872 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:25:28 +0100 Subject: [PATCH 071/175] fix(converttobyteunits): Rename module --- DiscordVeeamAlertSender.ps1 | 10 +++---- resources/ConvertTo-ByteUnits.psm1 | 45 ------------------------------ 2 files changed, 5 insertions(+), 50 deletions(-) delete mode 100644 resources/ConvertTo-ByteUnits.psm1 diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 8a4e87b..cedb10e 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -7,7 +7,7 @@ Param( # Import functions Import-Module "$PSScriptRoot\resources\logger.psm1" -Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnits.psm1" +Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" Import-Module "$PSScriptRoot\resources\Get-UpdateInformation.psm1" @@ -111,10 +111,10 @@ if ($jobType -eq 'VM') { [Float]$speed = $session.Info.Progress.AvgSpeed # Convert bytes to rounded units. - $jobSizeRound = ConvertTo-ByteUnits -InputObject $jobSize - $transferSizeRound = ConvertTo-ByteUnits -InputObject $transferSize - ## Convert speed from B/s to rounded units and append '/s' - $speedRound = (ConvertTo-ByteUnits -InputObject $speed) + '/s' + $jobSizeRound = ConvertTo-ByteUnit -InputObject $jobSize + $transferSizeRound = ConvertTo-ByteUnit -InputObject $transferSize + # Convert speed from B/s to rounded units and append '/s' + $speedRound = (ConvertTo-ByteUnit -InputObject $speed) + '/s' # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { diff --git a/resources/ConvertTo-ByteUnits.psm1 b/resources/ConvertTo-ByteUnits.psm1 deleted file mode 100644 index c22792c..0000000 --- a/resources/ConvertTo-ByteUnits.psm1 +++ /dev/null @@ -1,45 +0,0 @@ -function ConvertTo-ByteUnit - { - [CmdletBinding()] - Param ( - [Parameter ( - Position = 0, - Mandatory)] - [int64] - $InputObject - ) - - begin {} - - process - { - $Sign = [math]::Sign($InputObject) - $InputObject = [math]::Abs($InputObject) - switch ($InputObject) - { - {$_ -ge 1TB } - {$Unit = 'TB'; break} - {$_ -ge 1GB } - {$Unit = 'GB'; break} - {$_ -ge 1MB } - {$Unit = 'MB'; break} - {$_ -ge 1KB } - {$Unit = 'KB'; break} - default - {$Unit = 'B'} - } - - if ($Unit -ne 'B') - { - '{0:N2} {1}' -f ($Sign * $InputObject / "1$Unit"), $Unit - } - else - { - '{0} {1}' -f ($Sign * $InputObject), $Unit - } - - } # end >> process - - end {} - - } # end >> function From c68cf87a2d6063f85f9b7252f32012b5004cb303 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:26:53 +0100 Subject: [PATCH 072/175] fix(alertsender): Minor fixes & style --- DiscordVeeamAlertSender.ps1 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index cedb10e..743d908 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -99,13 +99,15 @@ Switch ($duration) { } } +## Initialise fieldArray variable. +$fieldArray = @() + # Define session statistics for the report. ## If VM backup, gather and include session info if ($jobType -eq 'VM') { - # Gatherr session info for VM backup. - [String]$status = $session.Result + # Gather session info. [Float]$jobSize = $session.BackupStats.DataSize [Float]$transferSize = $session.BackupStats.BackupSize [Float]$speed = $session.Info.Progress.AvgSpeed @@ -118,7 +120,7 @@ if ($jobType -eq 'VM') { # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { - $speedRound = 'Unknown.' + $speedRound = 'Unknown' } # Add session information to fieldArray. @@ -175,7 +177,7 @@ If ($jobType -eq 'Agent') { $fieldArray += @( [PSCustomObject]@{ name = 'Notice' - value = "Veeam's PowerShell snappin provides very little information about agent backups, so unfortunately this is all that can be provided for the time being." + value = "Due to limitations in Veeam's PowerShell module, this information is unfortunately all that can be provided." inline = 'false' } ) @@ -235,7 +237,8 @@ If ($request.Length -gt '0') { # Trigger update if there's a newer version available. -If ($currentVersion -lt $latestVersion -and $config.auto_update) { +If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $config.auto_update) { + # Copy update script out of working directory. Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 From c5cca5a5454157ab19cb9258a9ed7e7be83c2463 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:27:40 +0100 Subject: [PATCH 073/175] fix(alertsender): Mention on warning//fail logic --- DiscordVeeamAlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 743d908..8421a4c 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -211,7 +211,7 @@ If (($config.mention_on_fail -and $Status -eq 'Failed') -or ($config.mention_on_ # Create payload object. ## Mention user on job failure if configured to do so. -If ($config.mention_on_fail -and $status -eq 'Failed') { +If ($mention) { $payload = [PSCustomObject]@{ content = "<@!$($config.userid)> Job status $status" embeds = $embedArray From 0e2bf506d7d23b53a8169ee185a6096a736ac63c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:45:37 +0100 Subject: [PATCH 074/175] style(updateinformation): Match variable names --- resources/Get-UpdateInformation.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/Get-UpdateInformation.psm1 b/resources/Get-UpdateInformation.psm1 index 0c97132..6cc97ff 100644 --- a/resources/Get-UpdateInformation.psm1 +++ b/resources/Get-UpdateInformation.psm1 @@ -34,16 +34,16 @@ function Get-UpdateMessage { # Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. # Picks a phrase at random from the list above for the version statement in the footer of the backup report. - Switch ($currentVersion) { - {$_ -lt $latestVersion} { + Switch ($CurrentVersion) { + {$_ -lt $LatestVersion} { $updateMessage = (Get-Random -InputObject $phrases.older -Count 1) } - {$_ -eq $latestVersion} { + {$_ -eq $LatestVersion} { $updateMessage = (Get-Random -InputObject $phrases.current -Count 1) } - {$_ -gt $latestVersion} { + {$_ -gt $LatestVersion} { $updateMessage = (Get-Random -InputObject $phrases.newer -Count 1) } } From 6ff330c728d9f2bdc8988fc5143d29b43a6b4094 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:45:51 +0100 Subject: [PATCH 075/175] style(vbrsessioninfo): Remove trailing whitespace --- resources/Get-VBRSessionInfo.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/Get-VBRSessionInfo.psm1 b/resources/Get-VBRSessionInfo.psm1 index 7290e04..8da30ba 100644 --- a/resources/Get-VBRSessionInfo.psm1 +++ b/resources/Get-VBRSessionInfo.psm1 @@ -23,7 +23,7 @@ Function Get-VBRSessionInfo { {$_ -eq 'Agent'} { # Get the session details. $session = Get-VBRComputerBackupJobSession -Id $sessionId - + # Copy the job's name to it's own variable. $jobName = $job.Info.Name } From fbf2e1b87c379956a23effe4acee714033ffdccf Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:46:17 +0100 Subject: [PATCH 076/175] fix(alertsender): Resolve PSUseCompatibleCommands --- DiscordVeeamAlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordVeeamAlertSender.ps1 b/DiscordVeeamAlertSender.ps1 index 8421a4c..73a9767 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/DiscordVeeamAlertSender.ps1 @@ -53,7 +53,7 @@ $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session ## Wait for the backup session to finish. While ($session.State -ne 'Stopped') { Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' - Start-Sleep -m 200 + Start-Sleep -Milliseconds 500 $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session } From 7867a6bbb79fd40b185ff425041a37a8dcf561a5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 17 Jul 2021 16:55:51 +0100 Subject: [PATCH 077/175] feat(bootstrap): Write job name and ID to log --- DiscordNotificationBootstrap.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DiscordNotificationBootstrap.ps1 b/DiscordNotificationBootstrap.ps1 index 0b5af0e..2495b2c 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/DiscordNotificationBootstrap.ps1 @@ -41,6 +41,8 @@ Switch ($job.JobType) { $sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType $jobName = $sessionInfo.JobName +Write-LogMessage -Tag Info -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." + # Build argument string for the alert sender. $powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" From 61301b3f4cb20f3d8142745b1aa01dbe169848bb Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 16:21:25 +0100 Subject: [PATCH 078/175] chore(scripts): Shorten script filenames --- ...ordVeeamAlertSender.ps1 => AlertSender.ps1 | 14 ++++++------ ...NotificationBootstrap.ps1 => Bootstrap.ps1 | 12 +++++----- README.md | 6 ++--- ...eeamDiscordNotification.ps1 => Updater.ps1 | 22 ++++++++++++++----- resources/{logger.psm1 => Loggerr.psm1} | 0 ...UpdateInformation.psm1 => UpdateInfo.psm1} | 0 ...BRSessionInfo.psm1 => VBRSessionInfo.psm1} | 0 7 files changed, 32 insertions(+), 22 deletions(-) rename DiscordVeeamAlertSender.ps1 => AlertSender.ps1 (93%) rename DiscordNotificationBootstrap.ps1 => Bootstrap.ps1 (87%) rename UpdateVeeamDiscordNotification.ps1 => Updater.ps1 (96%) rename resources/{logger.psm1 => Loggerr.psm1} (100%) rename resources/{Get-UpdateInformation.psm1 => UpdateInfo.psm1} (100%) rename resources/{Get-VBRSessionInfo.psm1 => VBRSessionInfo.psm1} (100%) diff --git a/DiscordVeeamAlertSender.ps1 b/AlertSender.ps1 similarity index 93% rename from DiscordVeeamAlertSender.ps1 rename to AlertSender.ps1 index 73a9767..066fa22 100644 --- a/DiscordVeeamAlertSender.ps1 +++ b/AlertSender.ps1 @@ -5,11 +5,11 @@ Param( [String]$jobType ) -# Import functions -Import-Module "$PSScriptRoot\resources\logger.psm1" +# Import modules. +Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" -Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" -Import-Module "$PSScriptRoot\resources\Get-UpdateInformation.psm1" +Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" +Import-Module "$PSScriptRoot\resources\UpdateInfo.psm1" # Get config from file $config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json @@ -239,11 +239,11 @@ If ($request.Length -gt '0') { # Trigger update if there's a newer version available. If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $config.auto_update) { # Copy update script out of working directory. - Copy-Item $PSScriptRoot\UpdateVeeamDiscordNotification.ps1 $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 - Unblock-File $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1 + Copy-Item $PSScriptRoot\Updater.ps1 $PSScriptRoot\..\VDNotifs-Updater.ps1 + Unblock-File $PSScriptRoot\..\VDNotifs-Updater.ps1 # Run update script. - $updateArgs = "-file $PSScriptRoot\..\UpdateVeeamDiscordNotification.ps1", "-LatestVersion $latestVersion" + $updateArgs = "-file $PSScriptRoot\..\VDNotifs-Updater.ps1", "-LatestVersion $latestVersion" Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden } diff --git a/DiscordNotificationBootstrap.ps1 b/Bootstrap.ps1 similarity index 87% rename from DiscordNotificationBootstrap.ps1 rename to Bootstrap.ps1 index 2495b2c..895660c 100644 --- a/DiscordNotificationBootstrap.ps1 +++ b/Bootstrap.ps1 @@ -1,6 +1,7 @@ -# Import Functions. -Import-Module "$PSScriptRoot\resources\logger.psm1" -Import-Module "$PSScriptRoot\resources\Get-VBRSessionInfo.psm1" +# Import modules. +Import-Module Veeam.Backup.PowerShell +Import-Module "$PSScriptRoot\resources\Logger.psm1" +Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" # Get the config from our config file. $config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json @@ -14,9 +15,6 @@ if($config.debug_log) { Start-Logging $logFile } -# Import Veeam module. -Import-Module Veeam.Backup.PowerShell - # Get the command line used to start the Veeam session. $parentPID = (Get-CimInstance Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").CommandLine @@ -44,7 +42,7 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag Info -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender. -$powershellArguments = "-file $PSScriptRoot\DiscordVeeamAlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. diff --git a/README.md b/README.md index 405fa5b..30b086d 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ Configuration is set in ./config/conf.json ## [MS Teams fork.](https://github.com/tigattack/VeeamTeamsNotifications) ## Credits -[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is based. +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is loosely based. -[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `UpdateVeeamDiscordNotifications.ps1` script. +[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `Updater.ps1` script. -[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnit.psm1` function.](https://pastebin.com/srN5CKty) +[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnit` function.](https://pastebin.com/srN5CKty) diff --git a/UpdateVeeamDiscordNotification.ps1 b/Updater.ps1 similarity index 96% rename from UpdateVeeamDiscordNotification.ps1 rename to Updater.ps1 index 0b516b3..c914cc1 100644 --- a/UpdateVeeamDiscordNotification.ps1 +++ b/Updater.ps1 @@ -4,7 +4,7 @@ Param ( ) # Import functions -Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1" +Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\Logger.psm1" # Logging ## Set log file name @@ -292,10 +292,22 @@ Catch { # Unblock script files Write-Output 'Unblock script files.' -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\DiscordVeeamAlertSender.ps1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\resources\logger.psm1 -ErrorAction Continue -Unblock-File $PSScriptRoot\VeeamDiscordNotifications\UpdateVeeamDiscordNotification.ps1 -ErrorAction Continue + +## Get script files +$pwshFiles = Get-ChildItem $PSScriptRoot\VeeamDiscordNotifications\ -Recurse | Where-Object { $_.Name -match '^.*\.ps(m)?1$' } + +## Unblock them +Try { + foreach ($i in $pwshFiles) { + Unblock-File -Path $i.FullName + } +} +Catch { + $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() + Write-Output "$errorVar" + $fail = 'unblock_scripts' + Update-Fail +} # Populate conf.json with previous configuration Try { diff --git a/resources/logger.psm1 b/resources/Loggerr.psm1 similarity index 100% rename from resources/logger.psm1 rename to resources/Loggerr.psm1 diff --git a/resources/Get-UpdateInformation.psm1 b/resources/UpdateInfo.psm1 similarity index 100% rename from resources/Get-UpdateInformation.psm1 rename to resources/UpdateInfo.psm1 diff --git a/resources/Get-VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 similarity index 100% rename from resources/Get-VBRSessionInfo.psm1 rename to resources/VBRSessionInfo.psm1 From 5590ee649c3f3f9c7fe31bf1a18184d77ec8799e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 16:21:50 +0100 Subject: [PATCH 079/175] chore(logger): Typo --- resources/{Loggerr.psm1 => Logger.psm1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/{Loggerr.psm1 => Logger.psm1} (100%) diff --git a/resources/Loggerr.psm1 b/resources/Logger.psm1 similarity index 100% rename from resources/Loggerr.psm1 rename to resources/Logger.psm1 From ad323f48827d267b89b9d3e6a49299ceb98f1606 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 16:24:44 +0100 Subject: [PATCH 080/175] feat(updater): Better utilise logger module --- Updater.ps1 | 76 +++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/Updater.ps1 b/Updater.ps1 index c914cc1..0e2eb3e 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -14,7 +14,7 @@ $logFile = "$PSScriptRoot\Log_Update-$date.log" Start-Logging $logFile # Set error action preference. -Write-Output 'Set error action preference.' +Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' $ErrorActionPreference = 'Stop' # Notification function @@ -25,7 +25,7 @@ function Update-Notification { )] Param () If ($PSCmdlet.ShouldProcess('Discord', 'Send update notification')) { - Write-Output 'Building notification.' + Write-LogMessage -Tag 'Info' -Message 'Building notification.' # Create embed and fields array [System.Collections.ArrayList]$embedArray = @() [System.Collections.ArrayList]$fieldArray = @() @@ -75,7 +75,7 @@ function Update-Notification { $payload = [PSCustomObject]@{ embeds = $embedArray } - Write-Output 'Sending notification.' + Write-LogMessage -Tag 'Info' -Message 'Sending notification.' # Send iiit Try { Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' @@ -83,7 +83,7 @@ function Update-Notification { Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Warning 'Update notification failed to send to Discord.' - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" } } } @@ -97,18 +97,18 @@ function Update-Success { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Update success process')) { # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' + Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' $ErrorActionPreference = 'Continue' # Set result var for notification and script output $script:result = 'Success!' # Copy logs directory from copy of previously installed version to new install - Write-Output 'Copying logs from old version to new version.' + Write-LogMessage -Tag 'Info' -Message 'Copying logs from old version to new version.' Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force # Remove copy of previously installed version - Write-Output 'Removing old version.' + Write-LogMessage -Tag 'Info' -Message 'Removing old version.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force # Trigger the Update-Notification function and then End-Script function. @@ -126,7 +126,7 @@ function Update-Fail { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Update failure process')) { # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-Output 'Set error action preference.' + Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' $ErrorActionPreference = 'Continue' # Set result var for notification and script output @@ -175,23 +175,25 @@ function Stop-Script { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Cleanup & stop')) { # Clean up. - Write-Output 'Remove downloaded ZIP.' + Write-LogMessage -Tag 'Info' -Message 'Remove downloaded ZIP.' If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" } - Write-Output 'Remove UpdateVeeamDiscordNotification.ps1.' + Write-LogMessage -Tag 'Info' -Message 'Remove Updater.ps1.' Remove-Item -LiteralPath $PSCommandPath -Force + + # Report result + Write-LogMessage -Tag 'Info' -Message "Update result: $result" # Stop logging - Write-Output 'Stop logging.' + Write-LogMessage -Tag 'Info' -Message 'Stop logging.' Stop-Logging $logFile # Move log file Write-Output 'Move log file to log directory in VeeamDiscordNotifications.' Move-Item $logFile "$PSScriptRoot\VeeamDiscordNotifications\log\" - # Report result and exit script - Write-Output "Update result: $result" + # Exit script Write-Output 'Exiting.' Exit } @@ -199,102 +201,102 @@ function Stop-Script { # Pull current config to variable Try { - Write-Output 'Pull current config to variable.' + Write-LogMessage -Tag 'Info' -Message 'Pull current config to variable.' $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" Update-Fail } # Get currently downloaded version Try { - Write-Output 'Getting currently downloaded version of the script.' + Write-LogMessage -Tag 'Info' -Message 'Getting currently downloaded version of the script.' [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" Update-Fail } # Wait until the alert sender has finished running, or quit this if it's still running after 60s. It should never take that long. -while (Get-CimInstance win32_process -filter "name='powershell.exe' and commandline like '%DiscordVeeamAlertSender.ps1%'") { +while (Get-CimInstance win32_process -filter "name='powershell.exe' and commandline like '%AlertSender.ps1%'") { $timer++ Start-Sleep -Seconds 1 - If ($timer -eq '60') { - Write-Output 'Timeout reached. Updater quitting as DiscordVeeamAlertSender.ps1 is still running after 60 seconds.' + If ($timer -eq '90') { + Write-LogMessage -Tag 'Info' -Message "Timeout reached. Updater quitting as AlertSender.ps1 is still running after $timer seconds." } Update-Fail } # Pull latest version of script from GitHub Try { - Write-Output 'Pull latest version of script from GitHub.' + Write-LogMessage -Tag 'Info' -Message 'Pull latest version of script from GitHub.' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'download' Update-Fail } # Expand downloaded ZIP Try { - Write-Output 'Expand downloaded ZIP.' + Write-LogMessage -Tag 'Info' -Message 'Expand downloaded ZIP.' Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'unzip' Update-Fail } # Rename old version to keep as a backup while the update is in progress. Try { - Write-Output 'Rename old version to make room for the new version.' + Write-LogMessage -Tag 'Info' -Message 'Rename current to avoid conflict with new version.' Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'rename_old' Update-Fail } # Rename extracted update Try { - Write-Output 'Rename extracted update.' + Write-LogMessage -Tag 'Info' -Message 'Rename extracted download.' Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'rename_new' Update-Fail } # Pull configuration from new conf file Try { - Write-Output 'Pull configuration from new conf file.' + Write-LogMessage -Tag 'Info' -Message 'Pull configuration from new conf file.' $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } # Unblock script files -Write-Output 'Unblock script files.' +Write-LogMessage -Tag 'Info' -Message 'Unblock script files.' ## Get script files -$pwshFiles = Get-ChildItem $PSScriptRoot\VeeamDiscordNotifications\ -Recurse | Where-Object { $_.Name -match '^.*\.ps(m)?1$' } +$pwshFiles = Get-ChildItem $PSScriptRoot\VeeamDiscordNotifications\* -Recurse | Where-Object { $_.Name -match '^.*\.ps(m)?1$' } ## Unblock them Try { @@ -304,14 +306,14 @@ Try { } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'unblock_scripts' Update-Fail } # Populate conf.json with previous configuration Try { - Write-Output 'Populate conf.json with previous configuration.' + Write-LogMessage -Tag 'Info' -Message 'Populate conf.json with previous configuration.' $newConfig.webhook = $currentConfig.webhook $newConfig.userid = $currentConfig.userid if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { @@ -327,19 +329,19 @@ Try { } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } # Get newly downloaded version Try { - Write-Output 'Get newly downloaded version.' + Write-LogMessage -Tag 'Info' -Message 'Get newly downloaded version.' [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-Output "$errorVar" + Write-LogMessage -Tag 'Error' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } From f2de9b1887cec5b4be83de2b8499ac9b57a764ac Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 17:48:55 +0100 Subject: [PATCH 081/175] feat(config): Auto-update default false --- config/conf.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/conf.json b/config/conf.json index af73b52..3536854 100644 --- a/config/conf.json +++ b/config/conf.json @@ -5,5 +5,6 @@ "mention_on_fail": false, "mention_on_warning": false, "debug_log": false, - "auto_update": true + "auto_update": false, + "comment": "Auto updater is still experimental. Use at your own risk." } From 48f48853a82324051a84dbbf78ebec59a4a2f35e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 17:53:33 +0100 Subject: [PATCH 082/175] feat(config): Add config validation and more > Minor style and comment improvements. > Logging improvments. > Pass config to AlertSender rather than retrieving twice. --- AlertSender.ps1 | 15 ++++++------- Bootstrap.ps1 | 47 +++++++++++++++++++++++++++++------------ config/conf.schema.json | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 config/conf.schema.json diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 066fa22..0779211 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -2,7 +2,8 @@ Param( [String]$jobName, [String]$id, - [String]$jobType + [String]$jobType, + [PSObject]$Config ) # Import modules. @@ -11,11 +12,9 @@ Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" Import-Module "$PSScriptRoot\resources\UpdateInfo.psm1" -# Get config from file -$config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json # Start logging if logging is enabled in config -if($config.debug_log) { +If ($Config.debug_log) { ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" @@ -35,7 +34,7 @@ $footerAddition = Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion ## Define thumbnail object. $thumbObject = [PSCustomObject]@{ - url = $config.thumbnail + url = $Config.thumbnail } ## Define footer object. @@ -227,7 +226,7 @@ Else { # Send iiiit. -$request = Invoke-RestMethod -Uri $config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' +$request = Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' # Write error if message fails to send to Discord. If ($request.Length -gt '0') { @@ -237,7 +236,7 @@ If ($request.Length -gt '0') { # Trigger update if there's a newer version available. -If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $config.auto_update) { +If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $Config.auto_update) { # Copy update script out of working directory. Copy-Item $PSScriptRoot\Updater.ps1 $PSScriptRoot\..\VDNotifs-Updater.ps1 Unblock-File $PSScriptRoot\..\VDNotifs-Updater.ps1 @@ -248,6 +247,6 @@ If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $config. } # Stop logging. -if($config.debug_log) { +If ($Config.debug_log) { Stop-Logging } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 895660c..c5d75e2 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -1,18 +1,39 @@ +# Start logging + +## Set log file name +$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) +$logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" + +## Start logging to file +Start-Transcript -Path $logFile + # Import modules. Import-Module Veeam.Backup.PowerShell Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" -# Get the config from our config file. -$config = (Get-Content "$PSScriptRoot\config\conf.json") -Join "`n" | ConvertFrom-Json +# Retrieve configuration. +## Get config. +$config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json -# Start logging if logging is enabled in config. -if($config.debug_log) { - ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) - $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" - ## Start logging to file - Start-Logging $logFile +## Test config. +Try { + $configSchema = Get-Content -Raw "$PSScriptRoot\config\conf.schema.json" | ConvertFrom-Json + foreach ($i in $configSchema.required) { + If (-not (Get-Member -InputObject $config -Name "$i" -Membertype NoteProperty)) { + throw "Required configuration property is missing. Property: $i" + } + } +} +Catch { + Write-LogMessage -Tag "Error" -Message "Failed to validate configuration: $_" +} + + +# Stop logging and remove logfile if logging is disable in config. +If (-not $config.debug_log) { + Stop-Logging + Remove-Item $logFile -Force -ErrorAction SilentlyContinue } # Get the command line used to start the Veeam session. @@ -22,7 +43,7 @@ $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").Co # Get the Veeam job and session IDs $jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] $sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] -# Get the Veeam job details and hide warnings to mute the warning regarding depreciation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative. +# Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative. $job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} # Get the job time @@ -41,14 +62,14 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag Info -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." -# Build argument string for the alert sender. -$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType" +# Build argument string for the alert sender script. +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType", "-Config $config" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden # Stop logging. -if($config.debug_log) { +If ($config.debug_log) { Stop-Logging } diff --git a/config/conf.schema.json b/config/conf.schema.json new file mode 100644 index 0000000..ecc25af --- /dev/null +++ b/config/conf.schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "webhook": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "userid": { + "type": "string" + }, + "mention_on_fail": { + "type": "boolean" + }, + "mention_on_warning": { + "type": "boolean" + }, + "debug_log": { + "type": "boolean" + }, + "auto_update": { + "type": "boolean" + }, + "comment": { + "type": "string" + } + }, + "required": [ + "webhook", + "thumbnail", + "userid", + "mention_on_fail", + "mention_on_warning", + "debug_log", + "auto_update" + ] +} From b8c6b529a3ef65dff392cc0867e1fa4c3f1a0b42 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 18:18:16 +0100 Subject: [PATCH 083/175] feat(AlertSender): Improve mention message > if/else to switch. > Better job fail/warn mention message. --- AlertSender.ps1 | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 0779211..1d329a3 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -209,18 +209,17 @@ If (($config.mention_on_fail -and $Status -eq 'Failed') -or ($config.mention_on_ } # Create payload object. -## Mention user on job failure if configured to do so. -If ($mention) { - $payload = [PSCustomObject]@{ - content = "<@!$($config.userid)> Job status $status" - embeds = $embedArray +Switch ($mention) { + ## Mention user on job failure if configured to do so. + $true { + $payload = [PSCustomObject]@{ + content = "<@!$($Config.userid)> Job $Status!" + embeds = $embedArray + } } -} - -## Otherwise do not mention user. -Else { - $payload = [PSCustomObject]@{ - embeds = $embedArray + ## Otherwise do not mention user. + $False { + $payload = [PSCustomObject]@{ embeds = $embedArray } } } From 6b130f5cf0b45ee23451100079966db5a140ec41 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 18:19:57 +0100 Subject: [PATCH 084/175] feat(alertsender): Improve decision to mention user --- AlertSender.ps1 | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 1d329a3..3aa1d3b 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -191,6 +191,28 @@ Switch ($status) { Default {$colour = '16777215'} } +# Decide whether to mention user +## On fail +Try { + If ($Config.mention_on_fail -and $Status -eq 'Failed') { + $mention = $true + } +} +Catch { + Write-LogMessage -Tag 'Warning' -Message "Unable to determine 'mention on fail' configuration. User will not be mentioned." +} + +## On warning +Try { + If ($Config.mention_on_warning -and $Status -eq 'Warning') { + $mention = $true + } +} +Catch { + Write-LogMessage -Tag 'Warning' -Message "Unable to determine 'mention on warning' configuration. User will not be mentioned." +} + + # Build embed object. $embedArray = @( [PSCustomObject]@{ @@ -203,11 +225,6 @@ $embedArray = @( } ) -# Decide whether to mention user -If (($config.mention_on_fail -and $Status -eq 'Failed') -or ($config.mention_on_warning -and $Status -eq 'Warning')) { - $mention = $true -} - # Create payload object. Switch ($mention) { ## Mention user on job failure if configured to do so. From 26e9512f12a9237ea907cef29195bb34861de500 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 18:21:05 +0100 Subject: [PATCH 085/175] style(alertsender): Minor readability improvements --- AlertSender.ps1 | 107 ++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 3aa1d3b..25822b5 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -39,8 +39,8 @@ $thumbObject = [PSCustomObject]@{ ## Define footer object. $footerObject = [PSCustomObject]@{ - text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion). $footerAddition" - icon_url = 'https://avatars0.githubusercontent.com/u/10629864' + text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion). $footerAddition" + icon_url = 'https://avatars0.githubusercontent.com/u/10629864' } @@ -64,12 +64,12 @@ $jobStartTime = $session.Info.CreationTime ## Create writeable object using their values, prepending 0 to single-digit values. ## Necessary because $jobEndTime and $jobStartTime are readonly. $jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour.ToString("00") - StartMinute = $jobStartTime.Minute.ToString("00") - StartSecond = $jobStartTime.Second.ToString("00") - EndHour = $jobEndTime.Hour.ToString("00") - EndMinute = $jobEndTime.Minute.ToString("00") - EndSecond = $jobEndTime.Second.ToString("00") + StartHour = $jobStartTime.Hour.ToString("00") + StartMinute = $jobStartTime.Minute.ToString("00") + StartSecond = $jobStartTime.Second.ToString("00") + EndHour = $jobEndTime.Hour.ToString("00") + EndMinute = $jobEndTime.Minute.ToString("00") + EndSecond = $jobEndTime.Second.ToString("00") } ## Calculate difference between job start and end time. @@ -78,23 +78,23 @@ $duration = $jobEndTime - $jobStartTime ## Switch for job duration; define pretty output. Switch ($duration) { {$_.Days -ge '1'} { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds break } {$_.Hours -ge '1'} { - $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds + $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds break } {$_.Minutes -ge '1'} { - $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds + $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds break } {$_.Seconds -ge '1'} { - $durationFormatted = '{0}s' -f $_.Seconds + $durationFormatted = '{0}s' -f $_.Seconds break } Default { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds } } @@ -107,17 +107,16 @@ $fieldArray = @() ## If VM backup, gather and include session info if ($jobType -eq 'VM') { # Gather session info. - [Float]$jobSize = $session.BackupStats.DataSize - [Float]$transferSize = $session.BackupStats.BackupSize - [Float]$speed = $session.Info.Progress.AvgSpeed + [Float]$jobSize = $session.BackupStats.DataSize + [Float]$transferSize = $session.BackupStats.BackupSize + [Float]$speed = $session.Info.Progress.AvgSpeed - # Convert bytes to rounded units. - $jobSizeRound = ConvertTo-ByteUnit -InputObject $jobSize - $transferSizeRound = ConvertTo-ByteUnit -InputObject $transferSize - # Convert speed from B/s to rounded units and append '/s' - $speedRound = (ConvertTo-ByteUnit -InputObject $speed) + '/s' + # Convert bytes to closest unit. + $jobSizeRound = ConvertTo-ByteUnit -InputObject $jobSize + $transferSizeRound = ConvertTo-ByteUnit -InputObject $transferSize + $speedRound = (ConvertTo-ByteUnit -InputObject $speed) + '/s' - # Set processing speed "Unknown" if 0B/s to avoid confusion. + # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { $speedRound = 'Unknown' } @@ -125,29 +124,29 @@ if ($jobType -eq 'VM') { # Add session information to fieldArray. $fieldArray = @( [PSCustomObject]@{ - name = 'Backup Size' - value = [String]$jobSizeRound - inline = 'true' + name = 'Backup Size' + value = [String]$jobSizeRound + inline = 'true' }, [PSCustomObject]@{ - name = 'Transferred Data' - value = [String]$transferSizeRound - inline = 'true' + name = 'Transferred Data' + value = [String]$transferSizeRound + inline = 'true' } [PSCustomObject]@{ - name = 'Dedup Ratio' - value = [String]$session.BackupStats.DedupRatio - inline = 'false' + name = 'Dedup Ratio' + value = [String]$session.BackupStats.DedupRatio + inline = 'false' } [PSCustomObject]@{ - name = 'Compression Ratio' - value = [String]$session.BackupStats.CompressRatio - inline = 'false' + name = 'Compression Ratio' + value = [String]$session.BackupStats.CompressRatio + inline = 'false' } [PSCustomObject]@{ - name = 'Processing Rate' - value = $speedRound - inline = 'false' + name = 'Processing Rate' + value = $speedRound + inline = 'false' } ) } @@ -155,19 +154,19 @@ if ($jobType -eq 'VM') { ## Add job times to fieldArray. $fieldArray += @( [PSCustomObject]@{ - name = 'Job Duration' - value = $durationFormatted - inline = 'true' + name = 'Job Duration' + value = $durationFormatted + inline = 'true' } [PSCustomObject]@{ - name = 'Time Started' - value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond - inline = 'true' + name = 'Time Started' + value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond + inline = 'true' } [PSCustomObject]@{ - name = 'Time Completed' - value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond - inline = 'true' + name = 'Time Completed' + value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond + inline = 'true' } ) @@ -175,20 +174,20 @@ $fieldArray += @( If ($jobType -eq 'Agent') { $fieldArray += @( [PSCustomObject]@{ - name = 'Notice' - value = "Due to limitations in Veeam's PowerShell module, this information is unfortunately all that can be provided." - inline = 'false' + name = 'Notice' + value = "Due to limitations in Veeam's PowerShell module, this information is unfortunately all that can be provided." + inline = 'false' } ) } # Switch for the session status to decide the embed colour. Switch ($status) { - None {$colour = '16777215'} - Warning {$colour = '16776960'} - Success {$colour = '65280'} - Failed {$colour = '16711680'} - Default {$colour = '16777215'} + None {$colour = '16777215'} + Warning {$colour = '16776960'} + Success {$colour = '65280'} + Failed {$colour = '16711680'} + Default {$colour = '16777215'} } # Decide whether to mention user From 64c889857d04a0cb61d46ee693dcb5abc74a5b53 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 18:21:41 +0100 Subject: [PATCH 086/175] feat(alertsender): Mention default to false Solves missing configuration property. --- AlertSender.ps1 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 25822b5..a4330df 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -23,6 +23,11 @@ If ($Config.debug_log) { } +# Initialise some variables. +$fieldArray = @() +$mention = $false + + # Determine if an update is required $updateStatus = Get-UpdateStatus @@ -98,9 +103,6 @@ Switch ($duration) { } } -## Initialise fieldArray variable. -$fieldArray = @() - # Define session statistics for the report. From 6f96a7b4d5b298d4513b4645f0decb3852a74b94 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:43:19 +0100 Subject: [PATCH 087/175] style(various): Log message tags and parameters --- AlertSender.ps1 | 14 ++++---- Bootstrap.ps1 | 4 +-- Updater.ps1 | 66 +++++++++++++++++------------------ resources/Logger.psm1 | 18 +++++----- resources/VBRSessionInfo.psm1 | 20 +++++------ 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index a4330df..c8f4f21 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -52,13 +52,13 @@ $footerObject = [PSCustomObject]@{ # Job info preparation ## Get the backup session information. -$session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session +$session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session ## Wait for the backup session to finish. While ($session.State -ne 'Stopped') { - Write-LogMessage -Tag 'Info' -Message 'Session not finished. Sleeping...' + Write-LogMessage -Tag 'INFO' -Message 'Session not finished. Sleeping...' Start-Sleep -Milliseconds 500 - $session = (Get-VBRSessionInfo -SessionID $id -JobType $jobType).Session + $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session } ## Gather generic session info @@ -200,7 +200,7 @@ Try { } } Catch { - Write-LogMessage -Tag 'Warning' -Message "Unable to determine 'mention on fail' configuration. User will not be mentioned." + Write-LogMessage -Tag 'WARN' -Message "Unable to determine 'mention on fail' configuration. User will not be mentioned." } ## On warning @@ -210,7 +210,7 @@ Try { } } Catch { - Write-LogMessage -Tag 'Warning' -Message "Unable to determine 'mention on warning' configuration. User will not be mentioned." + Write-LogMessage -Tag 'WARN' -Message "Unable to determine 'mention on warning' configuration. User will not be mentioned." } @@ -247,8 +247,8 @@ $request = Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Js # Write error if message fails to send to Discord. If ($request.Length -gt '0') { - Write-LogMessage -Tag 'Error' -Message 'Failed to send message to Discord. Response below.' - Write-LogMessage -Tag 'Error' -Message "$request" + Write-LogMessage -Tag 'ERROR' -Message 'Failed to send message to Discord. Response below.' + Write-LogMessage -Tag 'ERROR' -Message "$request" } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index c5d75e2..3de25b5 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -26,7 +26,7 @@ Try { } } Catch { - Write-LogMessage -Tag "Error" -Message "Failed to validate configuration: $_" + Write-LogMessage -Tag 'ERROR' -Message "Failed to validate configuration: $_" } @@ -60,7 +60,7 @@ Switch ($job.JobType) { $sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType $jobName = $sessionInfo.JobName -Write-LogMessage -Tag Info -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." +Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender script. $powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType", "-Config $config" diff --git a/Updater.ps1 b/Updater.ps1 index 0e2eb3e..6bd5012 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -14,7 +14,7 @@ $logFile = "$PSScriptRoot\Log_Update-$date.log" Start-Logging $logFile # Set error action preference. -Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' +Write-LogMessage -Tag 'INFO' -Message 'Set error action preference.' $ErrorActionPreference = 'Stop' # Notification function @@ -25,7 +25,7 @@ function Update-Notification { )] Param () If ($PSCmdlet.ShouldProcess('Discord', 'Send update notification')) { - Write-LogMessage -Tag 'Info' -Message 'Building notification.' + Write-LogMessage -Tag 'INFO' -Message 'Building notification.' # Create embed and fields array [System.Collections.ArrayList]$embedArray = @() [System.Collections.ArrayList]$fieldArray = @() @@ -75,7 +75,7 @@ function Update-Notification { $payload = [PSCustomObject]@{ embeds = $embedArray } - Write-LogMessage -Tag 'Info' -Message 'Sending notification.' + Write-LogMessage -Tag 'INFO' -Message 'Sending notification.' # Send iiit Try { Invoke-RestMethod -Uri $currentConfig.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' @@ -83,7 +83,7 @@ function Update-Notification { Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() Write-Warning 'Update notification failed to send to Discord.' - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" } } } @@ -97,18 +97,18 @@ function Update-Success { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Update success process')) { # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' + Write-LogMessage -Tag 'INFO' -Message 'Set error action preference.' $ErrorActionPreference = 'Continue' # Set result var for notification and script output $script:result = 'Success!' # Copy logs directory from copy of previously installed version to new install - Write-LogMessage -Tag 'Info' -Message 'Copying logs from old version to new version.' + Write-LogMessage -Tag 'INFO' -Message 'Copying logs from old version to new version.' Copy-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old\log -Destination $PSScriptRoot\VeeamDiscordNotifications\ -Recurse -Force # Remove copy of previously installed version - Write-LogMessage -Tag 'Info' -Message 'Removing old version.' + Write-LogMessage -Tag 'INFO' -Message 'Removing old version.' Remove-Item -Path $PSScriptRoot\VeeamDiscordNotifications-old -Recurse -Force # Trigger the Update-Notification function and then End-Script function. @@ -126,7 +126,7 @@ function Update-Fail { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Update failure process')) { # Set error action preference so that errors while ending the script don't end the script prematurely. - Write-LogMessage -Tag 'Info' -Message 'Set error action preference.' + Write-LogMessage -Tag 'INFO' -Message 'Set error action preference.' $ErrorActionPreference = 'Continue' # Set result var for notification and script output @@ -175,18 +175,18 @@ function Stop-Script { Param () If ($PSCmdlet.ShouldProcess('Updater', 'Cleanup & stop')) { # Clean up. - Write-LogMessage -Tag 'Info' -Message 'Remove downloaded ZIP.' + Write-LogMessage -Tag 'INFO' -Message 'Remove downloaded ZIP.' If (Test-Path "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip") { Remove-Item "$PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip" } - Write-LogMessage -Tag 'Info' -Message 'Remove Updater.ps1.' + Write-LogMessage -Tag 'INFO' -Message 'Remove Updater.ps1.' Remove-Item -LiteralPath $PSCommandPath -Force # Report result - Write-LogMessage -Tag 'Info' -Message "Update result: $result" + Write-LogMessage -Tag 'INFO' -Message "Update result: $result" # Stop logging - Write-LogMessage -Tag 'Info' -Message 'Stop logging.' + Write-LogMessage -Tag 'INFO' -Message 'Stop logging.' Stop-Logging $logFile # Move log file @@ -201,23 +201,23 @@ function Stop-Script { # Pull current config to variable Try { - Write-LogMessage -Tag 'Info' -Message 'Pull current config to variable.' + Write-LogMessage -Tag 'INFO' -Message 'Pull current config to variable.' $currentConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" Update-Fail } # Get currently downloaded version Try { - Write-LogMessage -Tag 'Info' -Message 'Getting currently downloaded version of the script.' + Write-LogMessage -Tag 'INFO' -Message 'Getting currently downloaded version of the script.' [String]$oldVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" Update-Fail } @@ -226,74 +226,74 @@ while (Get-CimInstance win32_process -filter "name='powershell.exe' and commandl $timer++ Start-Sleep -Seconds 1 If ($timer -eq '90') { - Write-LogMessage -Tag 'Info' -Message "Timeout reached. Updater quitting as AlertSender.ps1 is still running after $timer seconds." + Write-LogMessage -Tag 'INFO' -Message "Timeout reached. Updater quitting as AlertSender.ps1 is still running after $timer seconds." } Update-Fail } # Pull latest version of script from GitHub Try { - Write-LogMessage -Tag 'Info' -Message 'Pull latest version of script from GitHub.' + Write-LogMessage -Tag 'INFO' -Message 'Pull latest version of script from GitHub.' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'download' Update-Fail } # Expand downloaded ZIP Try { - Write-LogMessage -Tag 'Info' -Message 'Expand downloaded ZIP.' + Write-LogMessage -Tag 'INFO' -Message 'Expand downloaded ZIP.' Expand-Archive $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip -DestinationPath $PSScriptRoot } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'unzip' Update-Fail } # Rename old version to keep as a backup while the update is in progress. Try { - Write-LogMessage -Tag 'Info' -Message 'Rename current to avoid conflict with new version.' + Write-LogMessage -Tag 'INFO' -Message 'Rename current to avoid conflict with new version.' Rename-Item $PSScriptRoot\VeeamDiscordNotifications $PSScriptRoot\VeeamDiscordNotifications-old } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'rename_old' Update-Fail } # Rename extracted update Try { - Write-LogMessage -Tag 'Info' -Message 'Rename extracted download.' + Write-LogMessage -Tag 'INFO' -Message 'Rename extracted download.' Rename-Item $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion $PSScriptRoot\VeeamDiscordNotifications } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'rename_new' Update-Fail } # Pull configuration from new conf file Try { - Write-LogMessage -Tag 'Info' -Message 'Pull configuration from new conf file.' + Write-LogMessage -Tag 'INFO' -Message 'Pull configuration from new conf file.' $newConfig = (Get-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json") -Join "`n" | ConvertFrom-Json } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } # Unblock script files -Write-LogMessage -Tag 'Info' -Message 'Unblock script files.' +Write-LogMessage -Tag 'INFO' -Message 'Unblock script files.' ## Get script files $pwshFiles = Get-ChildItem $PSScriptRoot\VeeamDiscordNotifications\* -Recurse | Where-Object { $_.Name -match '^.*\.ps(m)?1$' } @@ -306,14 +306,14 @@ Try { } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'unblock_scripts' Update-Fail } # Populate conf.json with previous configuration Try { - Write-LogMessage -Tag 'Info' -Message 'Populate conf.json with previous configuration.' + Write-LogMessage -Tag 'INFO' -Message 'Populate conf.json with previous configuration.' $newConfig.webhook = $currentConfig.webhook $newConfig.userid = $currentConfig.userid if ($currentConfig.mention_on_fail -ne $newConfig.mention_on_fail) { @@ -329,19 +329,19 @@ Try { } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } # Get newly downloaded version Try { - Write-LogMessage -Tag 'Info' -Message 'Get newly downloaded version.' + Write-LogMessage -Tag 'INFO' -Message 'Get newly downloaded version.' [String]$newVersion = Get-Content "$PSScriptRoot\VeeamDiscordNotifications\resources\version.txt" -Raw } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() - Write-LogMessage -Tag 'Error' -Message "$errorVar" + Write-LogMessage -Tag 'ERROR' -Message "$errorVar" $fail = 'after_rename_new' Update-Fail } diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index bf45571..a3ad147 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -5,11 +5,11 @@ Function Write-LogMessage { ConfirmImpact = 'Low' )] Param ( - $tag, - $message + $Tag, + $Message ) If ($PSCmdlet.ShouldProcess('Output stream', 'Write log message')) { - Write-Output "[$tag] $message" + Write-Output "[$Tag] $Message" } } @@ -21,15 +21,15 @@ Function Start-Logging { )] Param( [Parameter(Mandatory)] - $path + $Path ) - If ($PSCmdlet.ShouldProcess($path, 'Start-Transcript')) { + If ($PSCmdlet.ShouldProcess($Path, 'Start-Transcript')) { Try { - Start-Transcript -Path $path -Force -Append - Write-LogMessage -Tag 'Info' -Message "Transcript is being logged to $path" + Start-Transcript -Path $Path -Force -Append + Write-LogMessage -Tag 'INFO' -Message "Transcript is being logged to $Path" } Catch [Exception] { - Write-LogMessage -Tag 'Info' -Message "Transcript is already being logged to $path" + Write-LogMessage -Tag 'INFO' -Message "Transcript is already being logged to $Path" } } } @@ -40,7 +40,7 @@ Function Stop-Logging { )] Param() If ($PSCmdlet.ShouldProcess('log file', 'Stop-Transcript')) { - Write-LogMessage -Tag 'Info' -Message "Stopping transcript logging." + Write-LogMessage -Tag 'INFO' -Message "Stopping transcript logging." Stop-Transcript } } diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index 8da30ba..271b03b 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -1,20 +1,20 @@ Function Get-VBRSessionInfo { param ( - [Parameter(Mandatory=$true)]$sessionId, - [Parameter(Mandatory=$true)]$jobType + [Parameter(Mandatory=$true)]$SessionId, + [Parameter(Mandatory=$true)]$JobType ) # Import VBR module Import-Module Veeam.Backup.PowerShell - If (($null -ne $sessionId) -and ($null -ne $jobType)) { + If (($null -ne $SessionId) -and ($null -ne $JobType)) { # Switch on job type. - Switch ($jobType) { + Switch ($JobType) { {$_ -eq 'VM'} { # Get the session details. - $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $sessionId} + $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $SessionId} # Get the job's name from the session details. $jobName = $session.OrigJobName @@ -22,7 +22,7 @@ Function Get-VBRSessionInfo { {$_ -eq 'Agent'} { # Get the session details. - $session = Get-VBRComputerBackupJobSession -Id $sessionId + $session = Get-VBRComputerBackupJobSession -Id $SessionId # Copy the job's name to it's own variable. $jobName = $job.Info.Name @@ -36,11 +36,11 @@ Function Get-VBRSessionInfo { } } - Elseif ($null -eq $sessionId) { - Write-Warning '$sessionId is null.' + Elseif ($null -eq $SessionId) { + Write-Warning '$SessionId is null.' } - Elseif ($null -eq $jobType) { - Write-Warning '$jobType is null.' + Elseif ($null -eq $JobType) { + Write-Warning '$JobType is null.' } } From b3496dbe8e8717b0be664b043c5c5cbff07ff4b6 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:47:19 +0100 Subject: [PATCH 088/175] fix(various): Hide Veeam module import warning Veeam.Backup.PowerShell module contains unapproved verbs. --- Bootstrap.ps1 | 2 +- resources/VBRSessionInfo.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 3de25b5..54e31f8 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -8,7 +8,7 @@ $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" Start-Transcript -Path $logFile # Import modules. -Import-Module Veeam.Backup.PowerShell +Import-Module Veeam.Backup.PowerShell -DisableNameChecking Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index 271b03b..f91631f 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -5,7 +5,7 @@ Function Get-VBRSessionInfo { ) # Import VBR module - Import-Module Veeam.Backup.PowerShell + Import-Module Veeam.Backup.PowerShell -DisableNameChecking If (($null -ne $SessionId) -and ($null -ne $JobType)) { From f2f733bd8a45d0115ab050ccb0c6a07484232711 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:50:14 +0100 Subject: [PATCH 089/175] fix(alertsender): Agent job timings --- AlertSender.ps1 | 96 ++++++++++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index c8f4f21..d5d392d 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -61,57 +61,20 @@ While ($session.State -ne 'Stopped') { $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session } -## Gather generic session info +## Gather generic session info. [String]$status = $session.Result -$jobEndTime = $session.Info.EndTime -$jobStartTime = $session.Info.CreationTime - -## Create writeable object using their values, prepending 0 to single-digit values. -## Necessary because $jobEndTime and $jobStartTime are readonly. -$jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour.ToString("00") - StartMinute = $jobStartTime.Minute.ToString("00") - StartSecond = $jobStartTime.Second.ToString("00") - EndHour = $jobEndTime.Hour.ToString("00") - EndMinute = $jobEndTime.Minute.ToString("00") - EndSecond = $jobEndTime.Second.ToString("00") -} - -## Calculate difference between job start and end time. -$duration = $jobEndTime - $jobStartTime - -## Switch for job duration; define pretty output. -Switch ($duration) { - {$_.Days -ge '1'} { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Hours -ge '1'} { - $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds - break - } - {$_.Minutes -ge '1'} { - $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds - break - } - {$_.Seconds -ge '1'} { - $durationFormatted = '{0}s' -f $_.Seconds - break - } - Default { - $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds - } -} # Define session statistics for the report. -## If VM backup, gather and include session info +## If VM backup, gather and include session info. if ($jobType -eq 'VM') { # Gather session info. [Float]$jobSize = $session.BackupStats.DataSize [Float]$transferSize = $session.BackupStats.BackupSize [Float]$speed = $session.Info.Progress.AvgSpeed + $jobEndTime = $session.Info.EndTime + $jobStartTime = $session.Info.CreationTime # Convert bytes to closest unit. $jobSizeRound = ConvertTo-ByteUnit -InputObject $jobSize @@ -153,6 +116,55 @@ if ($jobType -eq 'VM') { ) } +# If agent backup, gather and include session info. +If ($jobType -eq 'Agent') { + # Gather session info. + $jobEndTime = $session.EndTime + $jobStartTime = $session.CreationTime +} + + +# Job timings + +## Create array of job timings. +# Necessary because $jobEndTime and $jobStartTime are readonly. +# Create writeable object using their values, prepending 0 to single-digit values. +$jobTimes = [PSCustomObject]@{ + StartHour = $jobStartTime.Hour.ToString("00") + StartMinute = $jobStartTime.Minute.ToString("00") + StartSecond = $jobStartTime.Second.ToString("00") + EndHour = $jobEndTime.Hour.ToString("00") + EndMinute = $jobEndTime.Minute.ToString("00") + EndSecond = $jobEndTime.Second.ToString("00") +} + +# Calculate difference between job start and end time. +$duration = $jobEndTime - $jobStartTime + +## Switch for job duration; define pretty output. +Switch ($duration) { + {$_.Days -ge '1'} { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Hours -ge '1'} { + $durationFormatted = '{0}h {1}m {2}s' -f $_.Hours, $_.Minutes, $_.Seconds + break + } + {$_.Minutes -ge '1'} { + $durationFormatted = '{0}m {1}s' -f $_.Minutes, $_.Seconds + break + } + {$_.Seconds -ge '1'} { + $durationFormatted = '{0}s' -f $_.Seconds + break + } + Default { + $durationFormatted = '{0}d {1}h {2}m {3}s' -f $_.Days, $_.Hours, $_.Minutes, $_.Seconds + } +} + + ## Add job times to fieldArray. $fieldArray += @( [PSCustomObject]@{ @@ -172,6 +184,7 @@ $fieldArray += @( } ) + # If agent backup, add notice to fieldArray. If ($jobType -eq 'Agent') { $fieldArray += @( @@ -183,6 +196,7 @@ If ($jobType -eq 'Agent') { ) } + # Switch for the session status to decide the embed colour. Switch ($status) { None {$colour = '16777215'} From feaa0b156ee8793685054e79c1e25df6f9f8dc7c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 21:58:43 +0100 Subject: [PATCH 090/175] fix(alertsender): Fix version phrases --- AlertSender.ps1 | 2 +- resources/VersionPhrases.json | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index d5d392d..105a6ac 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -35,7 +35,7 @@ $updateStatus = Get-UpdateStatus # Define static output objects. ## Get and define update status message. -$footerAddition = Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion +$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) -replace 'latestVerPlaceholder', $updateStatus.LatestVersion ## Define thumbnail object. $thumbObject = [PSCustomObject]@{ diff --git a/resources/VersionPhrases.json b/resources/VersionPhrases.json index 35d4c2e..99711b2 100644 --- a/resources/VersionPhrases.json +++ b/resources/VersionPhrases.json @@ -1,10 +1,10 @@ { "older": [ - "Jesus mate, you're out of date! Latest is $latestVersion.", - "Bloody hell you muppet, you need to update! Latest is $latestVersion.", - "Fuck me sideways, you're out of date! Latest is $latestVersion.", - "Shitting heck lad, you need to update! Latest is $latestVersion.", - "Christ almighty, you're out of date! Latest is $latestVersion." + "Jesus mate, you're out of date! Latest is latestVerPlaceholder.", + "Bloody hell you muppet, you need to update! Latest is latestVerPlaceholder.", + "Fuck me sideways, you're out of date! Latest is latestVerPlaceholder.", + "Shitting heck lad, you need to update! Latest is latestVerPlaceholder.", + "Christ almighty, you're out of date! Latest is latestVerPlaceholder." ], "current": [ "Nice work mate, you're up to date.", @@ -14,10 +14,10 @@ "Lovely stuff mate, you're running the latest version." ], "newer": [ - "Wewlad, check you out running a pre-release version, latest is $latestVersion!", - "Christ m8e, this is mental, you're ahead of release, latest is $latestVersion!", - "You nutter, you're running a pre-release version! Latest is $latestVersion!", - "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is $latestVersion!", - "Fuuuckin hell, $currentVersion hasn't even been released! Latest is $latestVersion." + "Wewlad, check you out running a pre-release version, latest is latestVerPlaceholder!", + "Christ m8e, this is mental, you're ahead of release, latest is latestVerPlaceholder!", + "You nutter, you're running a pre-release version! Latest is latestVerPlaceholder!", + "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is latestVerPlaceholder!", + "Fuuuckin hell, $currentVersion hasn't even been released! Latest is latestVerPlaceholder." ] } From 13c1b258f71f2d0951c062ff4f87d4fd4b9a8205 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 23:13:45 +0100 Subject: [PATCH 091/175] chore(version): Bump version --- resources/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/version.txt b/resources/version.txt index 5363857..c0ec837 100644 --- a/resources/version.txt +++ b/resources/version.txt @@ -1 +1 @@ -v1.6 \ No newline at end of file +v2.0 From f9e24096113844da6de663292d6e2144dee7c880 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 23:18:37 +0100 Subject: [PATCH 092/175] feat(convertbyteunit): Rewrite module --- AlertSender.ps1 | 6 +-- Bootstrap.ps1 | 1 + resources/ConvertTo-ByteUnit.psm1 | 80 +++++++++++++++++-------------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 105a6ac..c731a6c 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -77,9 +77,9 @@ if ($jobType -eq 'VM') { $jobStartTime = $session.Info.CreationTime # Convert bytes to closest unit. - $jobSizeRound = ConvertTo-ByteUnit -InputObject $jobSize - $transferSizeRound = ConvertTo-ByteUnit -InputObject $transferSize - $speedRound = (ConvertTo-ByteUnit -InputObject $speed) + '/s' + $jobSizeRound = ConvertTo-ByteUnit -Data $jobSize + $transferSizeRound = ConvertTo-ByteUnit -Data $transferSize + $speedRound = (ConvertTo-ByteUnit -Data $speed) + '/s' # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 54e31f8..7ded6ad 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -43,6 +43,7 @@ $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").Co # Get the Veeam job and session IDs $jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] $sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] + # Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative. $job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} diff --git a/resources/ConvertTo-ByteUnit.psm1 b/resources/ConvertTo-ByteUnit.psm1 index c22792c..1524a24 100644 --- a/resources/ConvertTo-ByteUnit.psm1 +++ b/resources/ConvertTo-ByteUnit.psm1 @@ -1,45 +1,55 @@ -function ConvertTo-ByteUnit - { +function ConvertTo-ByteUnit { + <# + .Synopsis + Converts raw numbers to byte units. + .DESCRIPTION + Converts raw numbers to byte units, dynamically deciding which unit (i.e. B, KB, MB, etc.) based on the input figure. + .EXAMPLE + ConvertTo-ByteUnit -Data 1024 + .EXAMPLE + ConvertTo-ByteUnit -Data ((Get-ChildItem -Path ./ -Recurse | Measure-Object -Property Length -Sum).Sum) + #> [CmdletBinding()] Param ( - [Parameter ( - Position = 0, - Mandatory)] - [int64] - $InputObject - ) + [Parameter (Mandatory)] + $Data + ) begin {} - process - { - $Sign = [math]::Sign($InputObject) - $InputObject = [math]::Abs($InputObject) - switch ($InputObject) - { - {$_ -ge 1TB } - {$Unit = 'TB'; break} - {$_ -ge 1GB } - {$Unit = 'GB'; break} - {$_ -ge 1MB } - {$Unit = 'MB'; break} - {$_ -ge 1KB } - {$Unit = 'KB'; break} - default - {$Unit = 'B'} + process { + switch ($Data) { + {$_ -ge 1TB } { + $Value = $Data / 1TB + [String]$Value = [math]::Round($Value,2) + $Value += ' TB' + break } - - if ($Unit -ne 'B') - { - '{0:N2} {1}' -f ($Sign * $InputObject / "1$Unit"), $Unit + {$_ -ge 1GB } { + $Value = $Data / 1GB + [String]$Value = [math]::Round($Value,2) + $Value += ' GB' + break } - else - { - '{0} {1}' -f ($Sign * $InputObject), $Unit + {$_ -ge 1MB } { + $Value = $Data / 1MB + [String]$Value = [math]::Round($Value,2) + $Value += ' MB' + break } - - } # end >> process + {$_ -ge 1KB } { + $Value = $Data / 1KB + [String]$Value = [math]::Round($Value,2) + $Value += ' GB' + break + } + default { + $Value = $Data + break + } + } + Write-Output $Value + } end {} - - } # end >> function +} \ No newline at end of file From c534592d1877117f08b90f4a3cccc48bfa83eb3f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 23:26:30 +0100 Subject: [PATCH 093/175] fix(converttobyteunit): Typo GB > KB. --- resources/ConvertTo-ByteUnit.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/ConvertTo-ByteUnit.psm1 b/resources/ConvertTo-ByteUnit.psm1 index 1524a24..6240f81 100644 --- a/resources/ConvertTo-ByteUnit.psm1 +++ b/resources/ConvertTo-ByteUnit.psm1 @@ -40,7 +40,7 @@ function ConvertTo-ByteUnit { {$_ -ge 1KB } { $Value = $Data / 1KB [String]$Value = [math]::Round($Value,2) - $Value += ' GB' + $Value += ' KB' break } default { From 55d8c31c0aec74841a0471f315878ec82b4ece7c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 18 Jul 2021 23:40:43 +0100 Subject: [PATCH 094/175] fix(alertsender): Rename embed field to fit single line --- AlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index c731a6c..39103d0 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -178,7 +178,7 @@ $fieldArray += @( inline = 'true' } [PSCustomObject]@{ - name = 'Time Completed' + name = 'Time Ended' value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond inline = 'true' } From bc7bda98cc4e83f4f378ac9fb9adffb359e71bc6 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 12 Sep 2021 13:49:05 +0100 Subject: [PATCH 095/175] fix(bootstrap): Fix handing config to AlertSender --- AlertSender.ps1 | 5 ++++- Bootstrap.ps1 | 17 +++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 39103d0..ae7d729 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -3,9 +3,12 @@ Param( [String]$jobName, [String]$id, [String]$jobType, - [PSObject]$Config + $Config ) +# Convert config from JSON +$Config = $Config | ConvertFrom-Json + # Import modules. Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 7ded6ad..af6875f 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -1,10 +1,11 @@ -# Start logging +# Set config location +$configFile = "$PSScriptRoot\config\conf.json" -## Set log file name +# Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" -## Start logging to file +# Start logging to file Start-Transcript -Path $logFile # Import modules. @@ -13,8 +14,12 @@ Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" # Retrieve configuration. -## Get config. -$config = Get-Content -Raw "$PSScriptRoot\config\conf.json" | ConvertFrom-Json +## Pull config to PSCustomObject +$config = Get-Content -Raw $configFile | ConvertFrom-Json + +## Pull raw config and format for later. +## This is necessary since $config as a PSCustomObject was not passed through correctly with Start-Process and $powershellArguments. +$configRaw = (Get-Content -Raw $configFile).replace('"','\"') ## Test config. Try { @@ -64,7 +69,7 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender script. -$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType", "-Config $config" +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType", "-Config `"$($configRaw)`"" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. From bd2e5ac00d2b77036dcef8607f905af3d74ee8f6 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 13 Sep 2021 09:49:15 +0100 Subject: [PATCH 096/175] style(pssa): Spaces to tabs --- .github/scripts/pssa.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/scripts/pssa.ps1 b/.github/scripts/pssa.ps1 index 182c447..5b435aa 100644 --- a/.github/scripts/pssa.ps1 +++ b/.github/scripts/pssa.ps1 @@ -12,13 +12,13 @@ $infos = $issues.Where({$_.Severity -eq 'Information'}) # Report results to GitHub Actions Foreach ($i in $errors) { - Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" } Foreach ($i in $warnings) { - Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" } Foreach ($i in $infos) { - Write-Output "::debug file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + Write-Output "::debug file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" } Write-Output "There were $($errors.Count) errors, $($warnings.Count) warnings, and $($infos.Count) infos in total." From 3520c84116909cef4a6412ec6b2030f040b55ef0 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 13 Sep 2021 10:45:19 +0100 Subject: [PATCH 097/175] refactor(sessionInfo): Simplify casting session type --- Bootstrap.ps1 | 14 ++------------ resources/VBRSessionInfo.psm1 | 10 ++++++++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index af6875f..42901b3 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -49,21 +49,11 @@ $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").Co $jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] $sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] -# Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative. +# Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative way to discover the job time. $job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} -# Get the job time -Switch ($job.JobType) { - {$_ -eq 'Backup'} { - $jobType = 'VM' - } - {$_ -eq 'EpAgentBackup'} { - $jobType = 'Agent' - } -} - # Get the session information and name. -$sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $jobType +$sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $job.JobType $jobName = $sessionInfo.JobName Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index f91631f..3b9680b 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -11,7 +11,9 @@ Function Get-VBRSessionInfo { # Switch on job type. Switch ($JobType) { - {$_ -eq 'VM'} { + + # VM job + {$_ -eq 'Backup'} { # Get the session details. $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $SessionId} @@ -20,9 +22,13 @@ Function Get-VBRSessionInfo { $jobName = $session.OrigJobName } - {$_ -eq 'Agent'} { + # Agent job + {$_ -eq 'EpAgentBackup'} { # Get the session details. $session = Get-VBRComputerBackupJobSession -Id $SessionId + + $session = [Veeam.Backup.Core.CBackupSession]::GetByOriginalSessionId($SessionId) + $jobName = ($session.JobName | Select-String -Pattern '^(.+)(-.+)$').Matches.Groups[1].Value # Copy the job's name to it's own variable. $jobName = $job.Info.Name From b4108eaaedf5026bda089d7477cc8f19c2dd23be Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 13 Sep 2021 23:57:33 +0100 Subject: [PATCH 098/175] ci(pssa): Refactor PSSA CI script --- .github/scripts/pssa.ps1 | 50 ++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/.github/scripts/pssa.ps1 b/.github/scripts/pssa.ps1 index 5b435aa..0f0e8f8 100644 --- a/.github/scripts/pssa.ps1 +++ b/.github/scripts/pssa.ps1 @@ -2,27 +2,47 @@ Set-PSRepository PSGallery -InstallationPolicy Trusted Install-Module PSScriptAnalyzer -ErrorAction Stop +# Get all relevant PowerShell files +$psFiles = Get-ChildItem -Path ./* -Include *.ps1,*.psm1 -Recurse + # Run PSSA -Invoke-ScriptAnalyzer -Path * -Recurse -Settings ./.github/scripts/pssa-settings.psd1 -OutVariable issues | Out-Null +$issues = foreach ($i in $psFiles.FullName) { + Invoke-ScriptAnalyzer -Path $i -Recurse -Settings ./.github/scripts/pssa-settings.psd1 +} -# Get results and separate types -$errors = $issues.Where({$_.Severity -eq 'Error' -or $_.Severity -eq 'ParseError'}) -$warnings = $issues.Where({$_.Severity -eq 'Warning'}) -$infos = $issues.Where({$_.Severity -eq 'Information'}) +# Get results, types and report to GitHub Actions +$errors = $warnings = $infos = $unknowns = 0 -# Report results to GitHub Actions -Foreach ($i in $errors) { - Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" +foreach ($i in $issues) { + switch ($issue.Severity) { + {$_ -eq 'Error' -or $_ -eq 'ParseError'} { + Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + $errors++ + } + {$_ -eq 'Warning'} { + Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + $warnings++ + } + {$_ -eq 'Information'} { + Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + $infos++ + } + Default { + Write-Output "::debug file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + $unknowns++ + } + } } -Foreach ($i in $warnings) { - Write-Output "::warning file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" + +# Report summary to GitHub Actions +If ($unknowns -gt 0) { + Write-Output "There were $errors errors, $warnings warnings, $infos infos, and $unknowns unknowns in total." } -Foreach ($i in $infos) { - Write-Output "::debug file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" +Else { + Write-Output "There were $errors errors, $warnings warnings, and $infos infos in total." } -Write-Output "There were $($errors.Count) errors, $($warnings.Count) warnings, and $($infos.Count) infos in total." - -If ($errors) { +# Exit with error if any PSSA errors +If ($errors -gt 0) { exit 1 } From a597ff34d4df13acf79be938368cf41cbc5c7843 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 14 Sep 2021 00:03:08 +0100 Subject: [PATCH 099/175] ci(pssa): Add formatting rules, revise existing rules --- .github/scripts/pssa-settings.psd1 | 51 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/.github/scripts/pssa-settings.psd1 b/.github/scripts/pssa-settings.psd1 index c36fb36..54e3295 100644 --- a/.github/scripts/pssa-settings.psd1 +++ b/.github/scripts/pssa-settings.psd1 @@ -1,29 +1,60 @@ # Settings for PSScriptAnalyzer invocation. @{ + ExcludeRules = @( + 'PSReviewUnusedParameter' # Required due to PowerShell/PSScriptAnalyzer#1472. + ) Rules = @{ + PSAvoidUsingDoubleQuotesForConstantString = @{ + Enable = $true + } + PSAvoidUsingPositionalParameters = @{ + Enable = $true + } PSUseCompatibleCommands = @{ Enable = $true # PowerShell platforms we want to check compatibility with TargetProfiles = @( 'win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows Server 2016 'win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows Server 2019 - 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework', # PowerShell 5.1 on Windows 10 - 'win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2016 - 'win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2019 - 'win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows 10 - 'win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Windows Server 2016 - 'win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Server 2019 - 'win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core' # PowerShell 7.0 on Windows 10 + 'win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework'#, # PowerShell 5.1 on Windows 10 + #'win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2016 + #'win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows Server 2019 + #'win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core', # PowerShell 6.2 on Windows 10 + #'win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Windows Server 2016 + #'win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core', # PowerShell 7.0 on Server 2019 + #'win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core' # PowerShell 7.0 on Windows 10 ) } PSUseCompatibleSyntax = @{ Enable = $true # PowerShell versions we want to check compatibility with TargetVersions = @( - '5.1', - '6.2', - '7.1' + '5.1'#, + #'6.2', + #'7.1' ) } + PSPlaceCloseBrace = @{ + Enable = $true + NoEmptyLineBefore = $false + IgnoreOneLineBlock = $true + NewLineAfter = $true + } + PSPlaceOpenBrace = @{ + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + } + PSUseConsistentIndentation = @{ + Enable = $true + IndentationSize = 4 + PipelineIndentation = 'IncreaseIndentationForFirstPipeline' + Kind = 'tab' + } + PSAvoidLongLines = @{ + Enable = $true + MaximumLineLength = 155 + } } } From 41a1617650967dea8ec7f1032baab058defa05e5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 14 Sep 2021 00:14:24 +0100 Subject: [PATCH 100/175] fix: satisfy CI --- AlertSender.ps1 | 22 ++++++++++++---------- Bootstrap.ps1 | 7 ++++--- Updater.ps1 | 8 +++++--- resources/Logger.psm1 | 2 +- resources/UpdateInfo.psm1 | 3 ++- resources/VBRSessionInfo.psm1 | 4 ++-- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index ae7d729..ba61728 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -10,6 +10,7 @@ Param( $Config = $Config | ConvertFrom-Json # Import modules. +Import-Module Veeam.Backup.PowerShell -DisableNameChecking Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" @@ -19,7 +20,7 @@ Import-Module "$PSScriptRoot\resources\UpdateInfo.psm1" # Start logging if logging is enabled in config If ($Config.debug_log) { ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) + $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" ## Start logging to file Start-Logging $logFile @@ -38,7 +39,8 @@ $updateStatus = Get-UpdateStatus # Define static output objects. ## Get and define update status message. -$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) -replace 'latestVerPlaceholder', $updateStatus.LatestVersion +$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) ` + -replace 'latestVerPlaceholder', $updateStatus.LatestVersion ## Define thumbnail object. $thumbObject = [PSCustomObject]@{ @@ -133,12 +135,12 @@ If ($jobType -eq 'Agent') { # Necessary because $jobEndTime and $jobStartTime are readonly. # Create writeable object using their values, prepending 0 to single-digit values. $jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour.ToString("00") - StartMinute = $jobStartTime.Minute.ToString("00") - StartSecond = $jobStartTime.Second.ToString("00") - EndHour = $jobEndTime.Hour.ToString("00") - EndMinute = $jobEndTime.Minute.ToString("00") - EndSecond = $jobEndTime.Second.ToString("00") + StartHour = $jobStartTime.Hour.ToString('00') + StartMinute = $jobStartTime.Minute.ToString('00') + StartSecond = $jobStartTime.Second.ToString('00') + EndHour = $jobEndTime.Hour.ToString('00') + EndMinute = $jobEndTime.Minute.ToString('00') + EndSecond = $jobEndTime.Second.ToString('00') } # Calculate difference between job start and end time. @@ -193,7 +195,7 @@ If ($jobType -eq 'Agent') { $fieldArray += @( [PSCustomObject]@{ name = 'Notice' - value = "Due to limitations in Veeam's PowerShell module, this information is unfortunately all that can be provided." + value = "The information you see here is all that is available due to limitations in Veeam's PowerShell module." inline = 'false' } ) @@ -277,7 +279,7 @@ If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $Config. # Run update script. $updateArgs = "-file $PSScriptRoot\..\VDNotifs-Updater.ps1", "-LatestVersion $latestVersion" - Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden + Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden } # Stop logging. diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 42901b3..504361c 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -2,7 +2,7 @@ $configFile = "$PSScriptRoot\config\conf.json" # Set log file name -$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) +$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" # Start logging to file @@ -49,7 +49,8 @@ $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").Co $jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] $sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] -# Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. At time of writing, there is no alternative way to discover the job time. +# Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. +# At time of writing, there is no alternative way to discover the job time. $job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} # Get the session information and name. @@ -63,7 +64,7 @@ $powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. -Start-Process -FilePath "powershell" -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden +Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $powershellArguments -WindowStyle hidden # Stop logging. If ($config.debug_log) { diff --git a/Updater.ps1 b/Updater.ps1 index 6bd5012..40b3333 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -8,7 +8,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\Logger.psm1" # Logging ## Set log file name -$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ":", "." }) +$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) $logFile = "$PSScriptRoot\Log_Update-$date.log" ## Start logging to file Start-Logging $logFile @@ -181,7 +181,7 @@ function Stop-Script { } Write-LogMessage -Tag 'INFO' -Message 'Remove Updater.ps1.' Remove-Item -LiteralPath $PSCommandPath -Force - + # Report result Write-LogMessage -Tag 'INFO' -Message "Update result: $result" @@ -235,7 +235,9 @@ while (Get-CimInstance win32_process -filter "name='powershell.exe' and commandl Try { Write-LogMessage -Tag 'INFO' -Message 'Pull latest version of script from GitHub.' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip + Invoke-WebRequest -Uri ` + https://github.com/tigattack/VeeamDiscordNotifications/releases/download/$LatestVersion/VeeamDiscordNotifications-$LatestVersion.zip ` + -OutFile $PSScriptRoot\VeeamDiscordNotifications-$LatestVersion.zip } Catch { $errorVar = $_.CategoryInfo.Activity + ' : ' + $_.ToString() diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index a3ad147..b9dc92c 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -40,7 +40,7 @@ Function Stop-Logging { )] Param() If ($PSCmdlet.ShouldProcess('log file', 'Stop-Transcript')) { - Write-LogMessage -Tag 'INFO' -Message "Stopping transcript logging." + Write-LogMessage -Tag 'INFO' -Message 'Stopping transcript logging.' Stop-Transcript } } diff --git a/resources/UpdateInfo.psm1 b/resources/UpdateInfo.psm1 index 6cc97ff..36809ed 100644 --- a/resources/UpdateInfo.psm1 +++ b/resources/UpdateInfo.psm1 @@ -6,7 +6,8 @@ function Get-UpdateStatus { # Get latest release from GitHub. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - $latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest -Headers @{"Accept"="application/json"} -UseBasicParsing + $latestRelease = Invoke-WebRequest -Uri https://github.com/tigattack/VeeamDiscordNotifications/releases/latest ` + -Headers @{'Accept'='application/json'} -UseBasicParsing # Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"}, so we need to extract tag_name. $latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index 3b9680b..e9fd576 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -11,7 +11,7 @@ Function Get-VBRSessionInfo { # Switch on job type. Switch ($JobType) { - + # VM job {$_ -eq 'Backup'} { @@ -26,7 +26,7 @@ Function Get-VBRSessionInfo { {$_ -eq 'EpAgentBackup'} { # Get the session details. $session = Get-VBRComputerBackupJobSession -Id $SessionId - + $session = [Veeam.Backup.Core.CBackupSession]::GetByOriginalSessionId($SessionId) $jobName = ($session.JobName | Select-String -Pattern '^(.+)(-.+)$').Matches.Groups[1].Value From 526b6092948bb5848084a6f7360725fca2bafff0 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:12:59 +0100 Subject: [PATCH 101/175] feat(alertsender): Get more job attributes ++ * Job object counts * Embed field restructuring * Improve agent job details --- AlertSender.ps1 | 123 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index ba61728..b28c034 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -73,8 +73,8 @@ While ($session.State -ne 'Stopped') { # Define session statistics for the report. ## If VM backup, gather and include session info. -if ($jobType -eq 'VM') { - # Gather session info. +if ($jobType -eq 'Backup') { + # Gather session data sizes and timing. [Float]$jobSize = $session.BackupStats.DataSize [Float]$transferSize = $session.BackupStats.BackupSize [Float]$speed = $session.Info.Progress.AvgSpeed @@ -91,6 +91,26 @@ if ($jobType -eq 'VM') { $speedRound = 'Unknown' } + # Get objects in session. + $sessionObjects = $session.GetTaskSessions() + + ## Count total + $sessionObjectsCount = $sessionObjects.Count + + ## Count warns and fails + $sessionObjectWarns = 0 + $sessionObjectFails = 0 + + foreach ($object in $sessionObjects) { + If ($object.Status -eq 'Warning') { + $sessionObjectWarns++ + } + # TODO: check if 'Failed' is a valid state. + If ($object.Status -eq 'Failed') { + $sessionObjectFails++ + } + } + # Add session information to fieldArray. $fieldArray = @( [PSCustomObject]@{ @@ -106,26 +126,104 @@ if ($jobType -eq 'VM') { [PSCustomObject]@{ name = 'Dedup Ratio' value = [String]$session.BackupStats.DedupRatio - inline = 'false' + inline = 'true' } [PSCustomObject]@{ name = 'Compression Ratio' value = [String]$session.BackupStats.CompressRatio - inline = 'false' + inline = 'true' } [PSCustomObject]@{ name = 'Processing Rate' value = $speedRound - inline = 'false' + inline = 'true' + } + [PSCustomObject]@{ + name = 'Bottleneck' + value = [String]$session.Info.Progress.BottleneckInfo.Bottleneck + inline = 'true' } ) + + # Add object warns/fails to fieldArray if any. + If ($sessionObjectWarns -gt 0) { + $fieldArray += @( + [PSCustomObject]@{ + name = 'Warnings' + value = "$sessionObjectWarns/$sessionobjectsCount" + inline = 'true' + } + ) + } + If ($sessionObjectFails -gt 0) { + $fieldArray += @( + [PSCustomObject]@{ + name = 'Fails' + value = "$sessionObjectFails/$sessionobjectsCount" + inline = 'true' + } + ) + } } # If agent backup, gather and include session info. -If ($jobType -eq 'Agent') { - # Gather session info. - $jobEndTime = $session.EndTime - $jobStartTime = $session.CreationTime +If ($jobType -eq 'EpAgentBackup') { + # Gather session data sizes and timings. + [Float]$jobProcessedSize = $session.Info.Progress.ProcessedSize + [Float]$jobTransferredSize = $session.Info.Progress.TransferedSize + [Float]$speed = $session.Info.Progress.AvgSpeed + $jobEndTime = $session.EndTime + $jobStartTime = $session.CreationTime + + # Convert bytes to closest unit. + $jobProcessedSizeRound = ConvertTo-ByteUnit -Data $jobProcessedSize + $jobTransferredSizeRound = ConvertTo-ByteUnit -Data $jobTransferredSize + $speedRound = (ConvertTo-ByteUnit -Data $speed) + '/s' + + # Get number of objects in job. + $jobObjects = (Get-VBRComputerBackupJob -Name "$jobName").BackupObject + + # Initialise job object count variable. + $jobObjectCount = 0 + + # Determine if object is individual computer or container. + foreach ($i in 0..($jobObjects.Count-1)) { + # Switch on object type. + Switch ($jobObjects[$i].GetType()) { + # Individual computer + 'VBRIndividualComputer' { + $jobObjectCount++ + } + # Protection group + 'VBRProtectionGroup' { + $jobObjectCount = $jobObjectsCount+$objects[$i].Container.Entity.Count + } + } + } + + # Add session information to fieldArray. + $fieldArray = @( + [PSCustomObject]@{ + name = 'Processed Size' + value = [String]$jobProcessedSizeRound + inline = 'true' + }, + [PSCustomObject]@{ + name = 'Transferred Data' + value = [String]$jobTransferredSizeRound + inline = 'true' + }, + [PSCustomObject]@{ + name = 'Processing Rate' + value = $speedRound + inline = 'true' + }, + [PSCustomObject]@{ + name = 'Objects' + value = "$jobObjectCount" + inline = 'true' + } + ) } @@ -191,8 +289,13 @@ $fieldArray += @( # If agent backup, add notice to fieldArray. -If ($jobType -eq 'Agent') { +If ($jobType -eq 'EpAgentBackup') { $fieldArray += @( + [PSCustomObject]@{ + name = 'Job Objects' + value = $jobObjectsCount + inline = 'false' + } [PSCustomObject]@{ name = 'Notice' value = "The information you see here is all that is available due to limitations in Veeam's PowerShell module." From c0a79626761ffc9df74b02681df960a70060092b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 12:52:40 +0100 Subject: [PATCH 102/175] feat(bootstrap): Log version --- Bootstrap.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 504361c..79bb63f 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -8,6 +8,9 @@ $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" # Start logging to file Start-Transcript -Path $logFile +# Log version +Write-LogMessage -Tag 'INFO' -Message "Version: $(Get-Content "$PSScriptRoot\resources\version.txt" -Raw)" + # Import modules. Import-Module Veeam.Backup.PowerShell -DisableNameChecking Import-Module "$PSScriptRoot\resources\Logger.psm1" From 1d84d4557ebc168bb509de5b229fa924cac0672f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 12:54:10 +0100 Subject: [PATCH 103/175] feat(logger): Tag always upper case --- resources/Logger.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index b9dc92c..09a34a7 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -9,7 +9,7 @@ Function Write-LogMessage { $Message ) If ($PSCmdlet.ShouldProcess('Output stream', 'Write log message')) { - Write-Output "[$Tag] $Message" + Write-Output "[$($Tag.ToUpper())] $Message" } } From c3e3e07659951fb1358835a6269d2eddf3ce538d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:09:09 +0100 Subject: [PATCH 104/175] feat(logger): Output style improvements --- resources/Logger.psm1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index 09a34a7..69e357d 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -25,11 +25,11 @@ Function Start-Logging { ) If ($PSCmdlet.ShouldProcess($Path, 'Start-Transcript')) { Try { - Start-Transcript -Path $Path -Force -Append - Write-LogMessage -Tag 'INFO' -Message "Transcript is being logged to $Path" + Start-Transcript -Path $Path -Force -Append | Out-Null + Write-LogMessage -Tag 'INFO' -Message "Transcript is being logged to '$Path'." } - Catch [Exception] { - Write-LogMessage -Tag 'INFO' -Message "Transcript is already being logged to $Path" + Catch [System.IO.IOException] { + Write-LogMessage -Tag 'INFO' -Message "Transcript is already being logged to '$Path'." } } } From 843832a089cd29469c0109ead78fa7b6f6751612 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:09:27 +0100 Subject: [PATCH 105/175] fix(vbrsessioninfo): Use logger module --- resources/VBRSessionInfo.psm1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index e9fd576..b081449 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -43,10 +43,10 @@ Function Get-VBRSessionInfo { } Elseif ($null -eq $SessionId) { - Write-Warning '$SessionId is null.' + Write-LogMessage -Tag 'Warning' -Message 'SessionId is null.' } Elseif ($null -eq $JobType) { - Write-Warning '$JobType is null.' + Write-LogMessage -Tag 'Warning' -Message 'JobType is null.' } } From c9375a1426e5772eefb46bc5ceb2227f0702124e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:10:04 +0100 Subject: [PATCH 106/175] fix(bootstrap): Use logger module everywhere --- Bootstrap.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 79bb63f..c75bc70 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -6,7 +6,7 @@ $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' } $logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" # Start logging to file -Start-Transcript -Path $logFile +Start-Logging -Path $logFile # Log version Write-LogMessage -Tag 'INFO' -Message "Version: $(Get-Content "$PSScriptRoot\resources\version.txt" -Raw)" From 83e322aabf47c6ade579b6f941c600e2d5a76965 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:10:42 +0100 Subject: [PATCH 107/175] feat(bootstrap): Remove log earlier if not wanted --- Bootstrap.ps1 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index c75bc70..2fa8a15 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -20,6 +20,12 @@ Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" ## Pull config to PSCustomObject $config = Get-Content -Raw $configFile | ConvertFrom-Json +# Stop logging and remove log file if logging is disable in config. +If (-not $config.debug_log) { + Stop-Logging + Remove-Item $logFile -Force -ErrorAction SilentlyContinue +} + ## Pull raw config and format for later. ## This is necessary since $config as a PSCustomObject was not passed through correctly with Start-Process and $powershellArguments. $configRaw = (Get-Content -Raw $configFile).replace('"','\"') @@ -37,13 +43,6 @@ Catch { Write-LogMessage -Tag 'ERROR' -Message "Failed to validate configuration: $_" } - -# Stop logging and remove logfile if logging is disable in config. -If (-not $config.debug_log) { - Stop-Logging - Remove-Item $logFile -Force -ErrorAction SilentlyContinue -} - # Get the command line used to start the Veeam session. $parentPID = (Get-CimInstance Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").CommandLine From e593ef9829776104622b73db4eb3e2543f216c82 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:25:20 +0100 Subject: [PATCH 108/175] fix(bootstrap): Import modules first --- Bootstrap.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 2fa8a15..119f2df 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -1,3 +1,8 @@ +# Import modules +Import-Module Veeam.Backup.PowerShell -DisableNameChecking +Import-Module "$PSScriptRoot\resources\Logger.psm1" +Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" + # Set config location $configFile = "$PSScriptRoot\config\conf.json" @@ -11,11 +16,6 @@ Start-Logging -Path $logFile # Log version Write-LogMessage -Tag 'INFO' -Message "Version: $(Get-Content "$PSScriptRoot\resources\version.txt" -Raw)" -# Import modules. -Import-Module Veeam.Backup.PowerShell -DisableNameChecking -Import-Module "$PSScriptRoot\resources\Logger.psm1" -Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" - # Retrieve configuration. ## Pull config to PSCustomObject $config = Get-Content -Raw $configFile | ConvertFrom-Json From 7fcd966c0346bdf3f2e45f1e51cae12aa58884df Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:42:53 +0100 Subject: [PATCH 109/175] fix(bootstrap): Handle job names including spaces --- Bootstrap.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 119f2df..e516f25 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -62,7 +62,7 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender script. -$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName $jobName", "-Id $sessionId", "-JobType $jobType", "-Config `"$($configRaw)`"" +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName '$jobName'", "-Id '$sessionId'", "-JobType '$jobType'", "-Config `"$($configRaw)`"" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. From 9618f0962e3043ab70487b10f06021d10e60189d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 25 Sep 2021 13:46:25 +0100 Subject: [PATCH 110/175] fix(bootstrap): Fix jobtype --- Bootstrap.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index e516f25..ff109ae 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -62,7 +62,8 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender script. -$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName '$jobName'", "-Id '$sessionId'", "-JobType '$jobType'", "-Config `"$($configRaw)`"" +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName '$jobName'", "-Id '$sessionId'","-JobType '$($job.JobType)'", ` + "-Config `"$($configRaw)`"" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. From e584ae8464fd0d6af24fea9557886de3857978ae Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 14:34:39 +0100 Subject: [PATCH 111/175] feat(bootstrap): small improvements --- Bootstrap.ps1 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index ff109ae..3b77db6 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -3,12 +3,11 @@ Import-Module Veeam.Backup.PowerShell -DisableNameChecking Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" -# Set config location +# Set vars $configFile = "$PSScriptRoot\config\conf.json" - -# Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) -$logFile = "$PSScriptRoot\log\Log_Bootstrap-$date.log" +$logFile = "$PSScriptRoot\log\Log_Bootstrap_$date.log" +$idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' # Start logging to file Start-Logging -Path $logFile @@ -44,12 +43,12 @@ Catch { } # Get the command line used to start the Veeam session. -$parentPID = (Get-CimInstance Win32_Process -Filter "processid='$pid'").parentprocessid.ToString() +$parentPid = (Get-CimInstance Win32_Process -Filter "processid='$PID'").parentprocessid.ToString() $parentCmd = (Get-CimInstance Win32_Process -Filter "processid='$parentPID'").CommandLine # Get the Veeam job and session IDs -$jobId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[0] -$sessionId = ([regex]::Matches($parentCmd, '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')).Value[1] +$jobId = ([regex]::Matches($parentCmd, $idRegex)).Value[0] +$sessionId = ([regex]::Matches($parentCmd, $idRegex)).Value[1] # Get the Veeam job details and hide warnings to mute the warning regarding deprecation of the use of this cmdlet to get Agent job details. # At time of writing, there is no alternative way to discover the job time. From de860124770d1b8f01d6c7ebd2d46e5cfe658ad6 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 14:34:59 +0100 Subject: [PATCH 112/175] feat(alertsender): Better log file name --- AlertSender.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index b28c034..156577b 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -19,9 +19,16 @@ Import-Module "$PSScriptRoot\resources\UpdateInfo.psm1" # Start logging if logging is enabled in config If ($Config.debug_log) { + ## Replace spaces if any in the job name + If ($jobName -match ' ') { + $logJobName = $jobName.Replace(' ', '_') + } + Else { + $logJobName = $jobName + } ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) - $logFile = "$PSScriptRoot\log\Log_$jobName-$date.log" + $logFile = "$PSScriptRoot\log\Log_$($logJobName)_$date.log" ## Start logging to file Start-Logging $logFile } From 495dd5c46e710472cbcb91c83d8b7e601a35baad Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 18:01:12 +0100 Subject: [PATCH 113/175] refactor: get date for logfile name --- AlertSender.ps1 | 2 +- Bootstrap.ps1 | 2 +- Updater.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 156577b..8b465c6 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -27,7 +27,7 @@ If ($Config.debug_log) { $logJobName = $jobName } ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) + $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\log\Log_$($logJobName)_$date.log" ## Start logging to file Start-Logging $logFile diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 3b77db6..c763424 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -5,7 +5,7 @@ Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" # Set vars $configFile = "$PSScriptRoot\config\conf.json" -$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) +$date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\log\Log_Bootstrap_$date.log" $idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' diff --git a/Updater.ps1 b/Updater.ps1 index 40b3333..64f53bf 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -8,7 +8,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\Logger.psm1" # Logging ## Set log file name -$date = (Get-Date -UFormat %Y-%m-%d_%T | ForEach-Object { $_ -replace ':', '.' }) +$date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\Log_Update-$date.log" ## Start logging to file Start-Logging $logFile From a74e1f3569d0f18862beadc2f86b04090dfb69d3 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 18:01:57 +0100 Subject: [PATCH 114/175] refactor(alertsender): Improve webrequest error handing --- AlertSender.ps1 | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 8b465c6..85fc1b6 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -372,12 +372,11 @@ Switch ($mention) { # Send iiiit. -$request = Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' - -# Write error if message fails to send to Discord. -If ($request.Length -gt '0') { - Write-LogMessage -Tag 'ERROR' -Message 'Failed to send message to Discord. Response below.' - Write-LogMessage -Tag 'ERROR' -Message "$request" +Try { + Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' +} +Catch [System.Net.WebException] { + Write-LogMessage -Tag 'ERROR' -Message 'Unable to send webhook. Check your webhook URL or network connection.' } From 8bc01bf5dd5f4ca0814e6ca50c699864750d94a7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 18:02:30 +0100 Subject: [PATCH 115/175] feat(logger): include time in log output --- resources/Logger.psm1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index 69e357d..b250d01 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -8,8 +8,9 @@ Function Write-LogMessage { $Tag, $Message ) + $time = Get-Date -Format "HH:mm:ss.ff" If ($PSCmdlet.ShouldProcess('Output stream', 'Write log message')) { - Write-Output "[$($Tag.ToUpper())] $Message" + Write-Output "[$time] $($Tag.ToUpper()): $Message" } } From 37e4c69165c1bcadc28582d440decf4b4ab9546d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 19:18:02 +0100 Subject: [PATCH 116/175] refactor: minor logging improvements --- AlertSender.ps1 | 2 +- Bootstrap.ps1 | 10 +++++++++- Updater.ps1 | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 85fc1b6..31fa0ec 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -28,7 +28,7 @@ If ($Config.debug_log) { } ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') - $logFile = "$PSScriptRoot\log\Log_$($logJobName)_$date.log" + $logFile = "$PSScriptRoot\log\AlertSender_$($logJobName)_$date.log" ## Start logging to file Start-Logging $logFile } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index c763424..b9a0355 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -6,7 +6,7 @@ Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" # Set vars $configFile = "$PSScriptRoot\config\conf.json" $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') -$logFile = "$PSScriptRoot\log\Log_Bootstrap_$date.log" +$logFile = "$PSScriptRoot\log\Bootstrap_$date.log" $idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' # Start logging to file @@ -72,3 +72,11 @@ Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $powershellArgume If ($config.debug_log) { Stop-Logging } + +# Rename log file to include the job name. +Try { + Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\Bootstrap_$($jobName)_$date.log" +} +Catch { + Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" +} diff --git a/Updater.ps1 b/Updater.ps1 index 64f53bf..4658736 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -9,7 +9,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\Logger.psm1" # Logging ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') -$logFile = "$PSScriptRoot\Log_Update-$date.log" +$logFile = "$PSScriptRoot\Update-$date.log" ## Start logging to file Start-Logging $logFile From 6c5142cc90c9e3cd93f4f4193b023dda8bcf0e3a Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 21:57:33 +0100 Subject: [PATCH 117/175] fix(bootstrap): Fix PowerShell args Recent 'fix' caused issues with the argument list. --- Bootstrap.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index b9a0355..ae3ce84 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -27,7 +27,7 @@ If (-not $config.debug_log) { ## Pull raw config and format for later. ## This is necessary since $config as a PSCustomObject was not passed through correctly with Start-Process and $powershellArguments. -$configRaw = (Get-Content -Raw $configFile).replace('"','\"') +$configRaw = (Get-Content -Raw $configFile).Replace('"','\"').Replace("`n",'').Replace("`t",'').Replace(' ',' ') ## Test config. Try { @@ -61,7 +61,7 @@ $jobName = $sessionInfo.JobName Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." # Build argument string for the alert sender script. -$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName '$jobName'", "-Id '$sessionId'","-JobType '$($job.JobType)'", ` +$powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName `"$jobName`"", "-Id `"$sessionId`"","-JobType `"$($job.JobType)`"", ` "-Config `"$($configRaw)`"" # Start a new new script in a new process with some of the information gathered here. From 6bb54fff23b178b8f2c6572d07ced709f6a86925 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:08:07 +0100 Subject: [PATCH 118/175] refactor(config): Rename self update setting --- AlertSender.ps1 | 2 +- README.md | 2 +- Updater.ps1 | 4 ++-- config/conf.json | 4 ++-- config/conf.schema.json | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 31fa0ec..463d69a 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -381,7 +381,7 @@ Catch [System.Net.WebException] { # Trigger update if there's a newer version available. -If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $Config.auto_update) { +If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $Config.self_update) { # Copy update script out of working directory. Copy-Item $PSScriptRoot\Updater.ps1 $PSScriptRoot\..\VDNotifs-Updater.ps1 Unblock-File $PSScriptRoot\..\VDNotifs-Updater.ps1 diff --git a/README.md b/README.md index 30b086d..79cf1f8 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Configuration is set in ./config/conf.json | `mention_on_fail` | boolean | When `true`, you will be mentioned when a job finishes in a failed state. | | `mention_on_warning` | boolean | When `true`, you will be mentioned when a job finishes in a warning state. | | `debug_log` | boolean | When `true`, the script will log to a file in ./log/ | -| `auto_update` | boolean | When `true`, the script will check for updates on each run and update itself if there's a newer version available. | +| `self_update` | boolean | When `true`, the script will update itself if there's a newer version available. | --- ## [Slack fork.](https://github.com/tigattack/VeeamSlackNotifications) diff --git a/Updater.ps1 b/Updater.ps1 index 4658736..13e4343 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -324,8 +324,8 @@ Try { if ($currentConfig.debug_log -ne $newConfig.debug_log) { $newConfig.debug_log = $currentConfig.debug_log } - if ($currentConfig.auto_update -ne $newConfig.auto_update) { - $newConfig.auto_update = $currentConfig.auto_update + if ($currentConfig.self_update -ne $newConfig.self_update) { + $newConfig.self_update = $currentConfig.self_update } ConvertTo-Json $newConfig | Set-Content "$PSScriptRoot\VeeamDiscordNotifications\config\conf.json" } diff --git a/config/conf.json b/config/conf.json index 3536854..10b7cdb 100644 --- a/config/conf.json +++ b/config/conf.json @@ -5,6 +5,6 @@ "mention_on_fail": false, "mention_on_warning": false, "debug_log": false, - "auto_update": false, - "comment": "Auto updater is still experimental. Use at your own risk." + "self_update": false, + "comment": "Self-updater is still experimental. Use at your own risk." } diff --git a/config/conf.schema.json b/config/conf.schema.json index ecc25af..ea57943 100644 --- a/config/conf.schema.json +++ b/config/conf.schema.json @@ -20,7 +20,7 @@ "debug_log": { "type": "boolean" }, - "auto_update": { + "self_update": { "type": "boolean" }, "comment": { @@ -34,6 +34,6 @@ "mention_on_fail", "mention_on_warning", "debug_log", - "auto_update" + "self_update" ] } From 36d701cf0f6821f72287be82054a045868661ef9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:18:33 +0100 Subject: [PATCH 119/175] feat: add optional update notification --- AlertSender.ps1 | 58 ++++++++++++++++++++++++++++++++++------- config/conf.json | 1 + config/conf.schema.json | 3 +++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 463d69a..2e2cc8f 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -380,15 +380,55 @@ Catch [System.Net.WebException] { } -# Trigger update if there's a newer version available. -If (($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) -and $Config.self_update) { - # Copy update script out of working directory. - Copy-Item $PSScriptRoot\Updater.ps1 $PSScriptRoot\..\VDNotifs-Updater.ps1 - Unblock-File $PSScriptRoot\..\VDNotifs-Updater.ps1 - - # Run update script. - $updateArgs = "-file $PSScriptRoot\..\VDNotifs-Updater.ps1", "-LatestVersion $latestVersion" - Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden +# If newer version available... +If ($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) { + + # Trigger update if configured to do so. + If ($Config.self_update) { + + # Copy update script out of working directory. + Copy-Item $PSScriptRoot\Updater.ps1 $PSScriptRoot\..\VDNotifs-Updater.ps1 + Unblock-File $PSScriptRoot\..\VDNotifs-Updater.ps1 + + # Run update script. + $updateArgs = "-file $PSScriptRoot\..\VDNotifs-Updater.ps1", "-LatestVersion $latestVersion" + Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $updateArgs -WindowStyle hidden + } + + # Send update notice if configured to do so. + If ($Config.notify_update) { + + # Define + $updateNotice = [PSCustomObject]@{ + embeds = @( + [PSCustomObject]@{ + title = 'Update Available' + description = 'A new version of VeeamDiscordNotifications is available!' + color = 3429867 + thumbnail = $thumbObject + fields = @( + [PSCustomObject]@{ + name = 'Download' + value = '[Link.](https://github.com/tigattack/VeeamDiscordNotifications/releases/latest)' + } + ) + footer = [PSCustomObject]@{ + text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion)." + icon_url = 'https://avatars0.githubusercontent.com/u/10629864' + } + timestamp = $((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffK')) + } + ) + } + + # Send + Try { + Invoke-RestMethod -Uri $Config.webhook -Body ($updateNotice | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + } + Catch [System.Net.WebException] { + Write-LogMessage -Tag 'ERROR' -Message 'Unable to send webhook. Check your webhook URL or network connection.' + } + } } # Stop logging. diff --git a/config/conf.json b/config/conf.json index 10b7cdb..cc24e1d 100644 --- a/config/conf.json +++ b/config/conf.json @@ -5,6 +5,7 @@ "mention_on_fail": false, "mention_on_warning": false, "debug_log": false, + "notify_update": false, "self_update": false, "comment": "Self-updater is still experimental. Use at your own risk." } diff --git a/config/conf.schema.json b/config/conf.schema.json index ea57943..3cb98f1 100644 --- a/config/conf.schema.json +++ b/config/conf.schema.json @@ -20,6 +20,9 @@ "debug_log": { "type": "boolean" }, + "notify_update": { + "type": "boolean" + }, "self_update": { "type": "boolean" }, From b4e2d51ed84a862ef6c74edb39e8eaa0e6e30108 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:19:38 +0100 Subject: [PATCH 120/175] feat/docs: (un)required configuration points --- README.md | 19 ++++++++++--------- config/conf.schema.json | 7 +------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 79cf1f8..117ffad 100644 --- a/README.md +++ b/README.md @@ -18,15 +18,16 @@ This is work that I will inevitably get to at some point, but I'd hate to see pe ## Configuration options Configuration is set in ./config/conf.json -| Name | Type | Description | -|---------------------- |--------- |-------------------------------------------------------------------------------------------------------------------- | -| `webhook` | string | Your Discord webhook URL. | -| `thumbnail` | string | Image URL for the thumbnail shown in the report embed. | -| `userid` | string | Your Discord user ID. Only required if either of the following two options are `true`. | -| `mention_on_fail` | boolean | When `true`, you will be mentioned when a job finishes in a failed state. | -| `mention_on_warning` | boolean | When `true`, you will be mentioned when a job finishes in a warning state. | -| `debug_log` | boolean | When `true`, the script will log to a file in ./log/ | -| `self_update` | boolean | When `true`, the script will update itself if there's a newer version available. | +| Name | Type | Required | Description | +|--------------------- |-------- |--------- |----------------------------------------------------------------------------------------------------------- | +| `webhook` | string | True | Your Discord webhook URL. | +| `thumbnail` | string | True | Image URL for the thumbnail shown in the report embed. | +| `userid` | string | False | Your Discord user ID. Required if either of the following two options are `true`. | +| `mention_on_fail` | boolean | False | When `true`, you will be mentioned when a job finishes in a failed state. Requires that `userid` is set. | +| `mention_on_warning` | boolean | False | When `true`, you will be mentioned when a job finishes in a warning state. Requires that `userid` is set. | +| `debug_log` | boolean | False | When `true`, the script will log to a file in ./log/ | +| `notify_update` | boolean | False | When `true`, the script will notify (but not mention) you on Discord if there's a newer version available. | +| `self_update` | boolean | False | When `true`, the script will update itself if there's a newer version available. | --- ## [Slack fork.](https://github.com/tigattack/VeeamSlackNotifications) diff --git a/config/conf.schema.json b/config/conf.schema.json index 3cb98f1..140aafd 100644 --- a/config/conf.schema.json +++ b/config/conf.schema.json @@ -32,11 +32,6 @@ }, "required": [ "webhook", - "thumbnail", - "userid", - "mention_on_fail", - "mention_on_warning", - "debug_log", - "self_update" + "thumbnail" ] } From c2f94208019537599523167a4c799efdee7b679d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:19:56 +0100 Subject: [PATCH 121/175] fix(bootstrap): logging fixes --- Bootstrap.ps1 | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index ae3ce84..2e6a041 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -71,12 +71,19 @@ Start-Process -FilePath 'powershell' -Verb runAs -ArgumentList $powershellArgume # Stop logging. If ($config.debug_log) { Stop-Logging -} -# Rename log file to include the job name. -Try { - Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\Bootstrap_$($jobName)_$date.log" -} -Catch { - Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" + # Rename log file to include the job name. + Try { + ## Replace spaces if any in the job name + If ($jobName -match ' ') { + $logJobName = $jobName.Replace(' ', '_') + } + Else { + $logJobName = $jobName + } + Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\Bootstrap_$($logJobName)_$date.log" + } + Catch { + Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" + } } From 6e8659a64e81cd5166c54bf4cc3b7f6647d57d73 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:21:24 +0100 Subject: [PATCH 122/175] docs(readme): Update --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 117ffad..d480409 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Veeam Backup & Replication Notifications for Discord + Sends notifications from Veeam Backup & Replication to Discord. Based upon [my fork](https://github.com/tigattack/VeeamSlackNotifications) of [TheSageColleges' original project](https://github.com/TheSageColleges/VeeamSlackNotifications). @@ -16,6 +17,7 @@ This is work that I will inevitably get to at some point, but I'd hate to see pe ![Chat Example](https://github.com/tigattack/VeeamDiscordNotifications/blob/master/asset/example.png) ## Configuration options + Configuration is set in ./config/conf.json | Name | Type | Required | Description | @@ -30,12 +32,15 @@ Configuration is set in ./config/conf.json | `self_update` | boolean | False | When `true`, the script will update itself if there's a newer version available. | --- + ## [Slack fork.](https://github.com/tigattack/VeeamSlackNotifications) + ## [MS Teams fork.](https://github.com/tigattack/VeeamTeamsNotifications) ## Credits -[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is loosely based. -[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration and first works on the `Updater.ps1` script. +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is (now loosely) based. + +[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration for and first works on the `Updater.ps1` script. -[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the original [`ConvertTo-ByteUnit` function.](https://pastebin.com/srN5CKty) +[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the first revision of the [`ConvertTo-ByteUnit` function](https://pastebin.com/srN5CKty). From 121d45b017c8140373bc3446d2906bd63811f8d9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:23:54 +0100 Subject: [PATCH 123/175] refactor: date first in logfile names --- AlertSender.ps1 | 2 +- Bootstrap.ps1 | 4 ++-- Updater.ps1 | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 2e2cc8f..27f3c8f 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -28,7 +28,7 @@ If ($Config.debug_log) { } ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') - $logFile = "$PSScriptRoot\log\AlertSender_$($logJobName)_$date.log" + $logFile = "$PSScriptRoot\log\$($date)_AlertSender_$($logJobName).log" ## Start logging to file Start-Logging $logFile } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 2e6a041..8375269 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -6,7 +6,7 @@ Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" # Set vars $configFile = "$PSScriptRoot\config\conf.json" $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') -$logFile = "$PSScriptRoot\log\Bootstrap_$date.log" +$logFile = "$PSScriptRoot\log\$($date)_Bootstrap.log" $idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' # Start logging to file @@ -81,7 +81,7 @@ If ($config.debug_log) { Else { $logJobName = $jobName } - Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\Bootstrap_$($logJobName)_$date.log" + Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)_Bootstrap_$($logJobName).log" } Catch { Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" diff --git a/Updater.ps1 b/Updater.ps1 index 13e4343..f62ea53 100644 --- a/Updater.ps1 +++ b/Updater.ps1 @@ -9,7 +9,7 @@ Import-Module "$PSScriptRoot\VeeamDiscordNotifications\resources\Logger.psm1" # Logging ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') -$logFile = "$PSScriptRoot\Update-$date.log" +$logFile = "$PSScriptRoot\$($date)_Update.log" ## Start logging to file Start-Logging $logFile From 26928e1f08cc817ac66870d1835184c4e584bc96 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:35:04 +0100 Subject: [PATCH 124/175] feat(config): notify on update default true --- AlertSender.ps1 | 2 +- README.md | 20 ++++++++++---------- config/conf.json | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 27f3c8f..b3d09ff 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -396,7 +396,7 @@ If ($updateStatus.CurrentVersion -lt $updateStatus.latestVersion) { } # Send update notice if configured to do so. - If ($Config.notify_update) { + If ($false -ne $Config.notify_update) { # Define $updateNotice = [PSCustomObject]@{ diff --git a/README.md b/README.md index d480409..9d6b3d9 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,16 @@ This is work that I will inevitably get to at some point, but I'd hate to see pe Configuration is set in ./config/conf.json -| Name | Type | Required | Description | -|--------------------- |-------- |--------- |----------------------------------------------------------------------------------------------------------- | -| `webhook` | string | True | Your Discord webhook URL. | -| `thumbnail` | string | True | Image URL for the thumbnail shown in the report embed. | -| `userid` | string | False | Your Discord user ID. Required if either of the following two options are `true`. | -| `mention_on_fail` | boolean | False | When `true`, you will be mentioned when a job finishes in a failed state. Requires that `userid` is set. | -| `mention_on_warning` | boolean | False | When `true`, you will be mentioned when a job finishes in a warning state. Requires that `userid` is set. | -| `debug_log` | boolean | False | When `true`, the script will log to a file in ./log/ | -| `notify_update` | boolean | False | When `true`, the script will notify (but not mention) you on Discord if there's a newer version available. | -| `self_update` | boolean | False | When `true`, the script will update itself if there's a newer version available. | +| Name | Type | Required | Default | Description | +|--------------------- |-------- |--------- |------------------ | ---------------------------------------------------------------------------------------------------------- | +| `webhook` | string | True | null | Your Discord webhook URL. | +| `thumbnail` | string | True | See example above | Image URL for the thumbnail shown in the report embed. | +| `userid` | string | False | null | Your Discord user ID. Required if either of the following two options are `true`. | +| `mention_on_fail` | boolean | False | False | When `true`, you will be mentioned when a job finishes in a failed state. Requires that `userid` is set. | +| `mention_on_warning` | boolean | False | False | When `true`, you will be mentioned when a job finishes in a warning state. Requires that `userid` is set. | +| `debug_log` | boolean | False | False | When `true`, the script will log to a file in ./log/ | +| `notify_update` | boolean | False | True | When `true`, the script will notify (but not mention) you on Discord if there's a newer version available. | +| `self_update` | boolean | False | False | When `true`, the script will update itself if there's a newer version available. | --- diff --git a/config/conf.json b/config/conf.json index cc24e1d..17fc8e5 100644 --- a/config/conf.json +++ b/config/conf.json @@ -5,7 +5,7 @@ "mention_on_fail": false, "mention_on_warning": false, "debug_log": false, - "notify_update": false, + "notify_update": true, "self_update": false, "comment": "Self-updater is still experimental. Use at your own risk." } From 1659d725ecb85ac64f91d99d7a5f47279185d18c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:40:57 +0100 Subject: [PATCH 125/175] fix(alertsender): Default thumb unless specified --- AlertSender.ps1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index b3d09ff..9a01f85 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -50,8 +50,15 @@ $footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersio -replace 'latestVerPlaceholder', $updateStatus.LatestVersion ## Define thumbnail object. -$thumbObject = [PSCustomObject]@{ - url = $Config.thumbnail +If ($Config.thumbnail) { + $thumbObject = [PSCustomObject]@{ + url = $Config.thumbnail + } +} +Else { + $thumbObject = [PSCustomObject]@{ + url = 'https://raw.githubusercontent.com/tigattack/VeeamDiscordNotifications/master/asset/thumb01.png' + } } ## Define footer object. From cf7b62c2994ed139ac903f5fa661df51d6f870a2 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:50:13 +0100 Subject: [PATCH 126/175] docs(readme): Fix error on config opts --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d6b3d9..98e5e2e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Configuration is set in ./config/conf.json | Name | Type | Required | Default | Description | |--------------------- |-------- |--------- |------------------ | ---------------------------------------------------------------------------------------------------------- | | `webhook` | string | True | null | Your Discord webhook URL. | -| `thumbnail` | string | True | See example above | Image URL for the thumbnail shown in the report embed. | +| `thumbnail` | string | False | See example above | Image URL for the thumbnail shown in the report embed. | | `userid` | string | False | null | Your Discord user ID. Required if either of the following two options are `true`. | | `mention_on_fail` | boolean | False | False | When `true`, you will be mentioned when a job finishes in a failed state. Requires that `userid` is set. | | `mention_on_warning` | boolean | False | False | When `true`, you will be mentioned when a job finishes in a warning state. Requires that `userid` is set. | From ca4e6336b16e77a048c28659bc74feba341929b8 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 26 Sep 2021 23:59:15 +0100 Subject: [PATCH 127/175] fix: footer release messages --- AlertSender.ps1 | 6 ++++-- resources/UpdateInfo.psm1 | 2 +- resources/VersionPhrases.json | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 9a01f85..fe9030e 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -46,8 +46,9 @@ $updateStatus = Get-UpdateStatus # Define static output objects. ## Get and define update status message. -$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) ` - -replace 'latestVerPlaceholder', $updateStatus.LatestVersion +$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) +$footerAddition = $footerAddition.Replace('latestVerPlaceholder',"$($updateStatus.LatestVersion)") +$footerAddition = $footerAddition.Replace('currentVerPlaceholder',"$($updateStatus.CurrentVersion)") ## Define thumbnail object. If ($Config.thumbnail) { @@ -359,6 +360,7 @@ $embedArray = @( thumbnail = $thumbObject fields = $fieldArray footer = $footerObject + timestamp = $((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffK')) } ) diff --git a/resources/UpdateInfo.psm1 b/resources/UpdateInfo.psm1 index 36809ed..057c90e 100644 --- a/resources/UpdateInfo.psm1 +++ b/resources/UpdateInfo.psm1 @@ -2,7 +2,7 @@ function Get-UpdateStatus { process { # Get currently downloaded version of this project. - $currentVersion = Get-Content "$PSScriptRoot\version.txt" -Raw + $currentVersion = (Get-Content "$PSScriptRoot\version.txt" -Raw).Replace("`n",'') # Get latest release from GitHub. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 diff --git a/resources/VersionPhrases.json b/resources/VersionPhrases.json index 99711b2..3daf5b5 100644 --- a/resources/VersionPhrases.json +++ b/resources/VersionPhrases.json @@ -17,7 +17,7 @@ "Wewlad, check you out running a pre-release version, latest is latestVerPlaceholder!", "Christ m8e, this is mental, you're ahead of release, latest is latestVerPlaceholder!", "You nutter, you're running a pre-release version! Latest is latestVerPlaceholder!", - "Bloody hell mate, this is unheard of, $currentVersion isn't even released yet, latest is latestVerPlaceholder!", - "Fuuuckin hell, $currentVersion hasn't even been released! Latest is latestVerPlaceholder." + "Bloody hell mate, this is unheard of, currentVerPlaceholder isn't even released yet, latest is latestVerPlaceholder!", + "Fuuuckin hell, currentVerPlaceholder hasn't even been released! Latest is latestVerPlaceholder." ] } From 63cbc74ba38ad0faafb02ec20b10553b969da5a9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 00:44:02 +0100 Subject: [PATCH 128/175] fix(alertsender): no endless wait for session stop --- AlertSender.ps1 | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index fe9030e..979c748 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -75,10 +75,20 @@ $footerObject = [PSCustomObject]@{ $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session ## Wait for the backup session to finish. -While ($session.State -ne 'Stopped') { - Write-LogMessage -Tag 'INFO' -Message 'Session not finished. Sleeping...' - Start-Sleep -Milliseconds 500 +$nonStoppedStates = 'Idle','Pausing','Postprocessing','Resuming','Starting','Stopping','WaitingRepository','WaitingTape ','Working' +$timeout = New-TimeSpan -Minutes 5 +$stopwatch = [System.Diagnostics.Stopwatch]::StartNew() +Do { $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session + Write-LogMessage -Tag 'INFO' -Message 'Session not finished. Sleeping...' + Start-Sleep -Seconds 10 +} +While ($session.State -in $nonStoppedStates) -and ($stopwatch.elapsed -lt $timeout) + +## Quit if still not stopped +If ($session.State -ne 'Stopped') { + Write-LogMessage -Tag 'ERROR' -Message 'Session not stopped. Aborting.' + Exit 1 } ## Gather generic session info. From 3489bcf82aa17799cd4721e947f0d963abc8177e Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:02:39 +0100 Subject: [PATCH 129/175] fix(alertsender): remove agent objects count Can only count objects in job, not in session, therefore useless. --- AlertSender.ps1 | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 979c748..2309d41 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -205,27 +205,6 @@ If ($jobType -eq 'EpAgentBackup') { $jobTransferredSizeRound = ConvertTo-ByteUnit -Data $jobTransferredSize $speedRound = (ConvertTo-ByteUnit -Data $speed) + '/s' - # Get number of objects in job. - $jobObjects = (Get-VBRComputerBackupJob -Name "$jobName").BackupObject - - # Initialise job object count variable. - $jobObjectCount = 0 - - # Determine if object is individual computer or container. - foreach ($i in 0..($jobObjects.Count-1)) { - # Switch on object type. - Switch ($jobObjects[$i].GetType()) { - # Individual computer - 'VBRIndividualComputer' { - $jobObjectCount++ - } - # Protection group - 'VBRProtectionGroup' { - $jobObjectCount = $jobObjectsCount+$objects[$i].Container.Entity.Count - } - } - } - # Add session information to fieldArray. $fieldArray = @( [PSCustomObject]@{ @@ -242,11 +221,6 @@ If ($jobType -eq 'EpAgentBackup') { name = 'Processing Rate' value = $speedRound inline = 'true' - }, - [PSCustomObject]@{ - name = 'Objects' - value = "$jobObjectCount" - inline = 'true' } ) } @@ -316,14 +290,9 @@ $fieldArray += @( # If agent backup, add notice to fieldArray. If ($jobType -eq 'EpAgentBackup') { $fieldArray += @( - [PSCustomObject]@{ - name = 'Job Objects' - value = $jobObjectsCount - inline = 'false' - } [PSCustomObject]@{ name = 'Notice' - value = "The information you see here is all that is available due to limitations in Veeam's PowerShell module." + value = "All useful information has been provided; further details are missing due to limitations in Veeam's PowerShell module." inline = 'false' } ) From f5c19344e9a5c18c4ca5d88fc9fc92e96e8f2507 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:29:35 +0100 Subject: [PATCH 130/175] fix(vbrsessioninfo): Remove unwanted commands --- README.md | 2 +- resources/VBRSessionInfo.psm1 | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 98e5e2e..485c70c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Sends notifications from Veeam Backup & Replication to Discord. Based upon [my fork](https://github.com/tigattack/VeeamSlackNotifications) of [TheSageColleges' original project](https://github.com/TheSageColleges/VeeamSlackNotifications). -### Looking for volunteers. +### Looking for volunteers If you enjoy this project and would like to help out, please do so. If you're interested in helping out, contact me on Discord - `tigatack#7987` diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index b081449..2bb84e4 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -25,10 +25,7 @@ Function Get-VBRSessionInfo { # Agent job {$_ -eq 'EpAgentBackup'} { # Get the session details. - $session = Get-VBRComputerBackupJobSession -Id $SessionId - $session = [Veeam.Backup.Core.CBackupSession]::GetByOriginalSessionId($SessionId) - $jobName = ($session.JobName | Select-String -Pattern '^(.+)(-.+)$').Matches.Groups[1].Value # Copy the job's name to it's own variable. $jobName = $job.Info.Name From efc18f6bd1ee702e74e1dbe1a29b86897649ad1c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:31:28 +0100 Subject: [PATCH 131/175] refactor(alertsender): Fix wait for session stop --- AlertSender.ps1 | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 2309d41..2d4fae3 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -75,15 +75,18 @@ $footerObject = [PSCustomObject]@{ $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session ## Wait for the backup session to finish. -$nonStoppedStates = 'Idle','Pausing','Postprocessing','Resuming','Starting','Stopping','WaitingRepository','WaitingTape ','Working' -$timeout = New-TimeSpan -Minutes 5 -$stopwatch = [System.Diagnostics.Stopwatch]::StartNew() -Do { - $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session - Write-LogMessage -Tag 'INFO' -Message 'Session not finished. Sleeping...' - Start-Sleep -Seconds 10 +If ($session.State -ne 'Stopped') { + $nonStoppedStates = 'Idle','Pausing','Postprocessing','Resuming','Starting','Stopping','WaitingRepository','WaitingTape ','Working' + $timeout = New-TimeSpan -Minutes 5 + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + Do { + Write-LogMessage -Tag 'INFO' -Message 'Session not finished. Sleeping...' + Start-Sleep -Seconds 10 + $session = (Get-VBRSessionInfo -SessionId $id -JobType $jobType).Session + } + While ($session.State -in $nonStoppedStates -and $stopwatch.elapsed -lt $timeout) + $stopwatch.Stop() } -While ($session.State -in $nonStoppedStates) -and ($stopwatch.elapsed -lt $timeout) ## Quit if still not stopped If ($session.State -ne 'Stopped') { From 4a6a4aeb7e2e7a23128f28e9f8811c5117e87fe9 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:32:54 +0100 Subject: [PATCH 132/175] perf(bootstrap): Handle unsupported job type --- Bootstrap.ps1 | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 8375269..0077ec0 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -8,6 +8,7 @@ $configFile = "$PSScriptRoot\config\conf.json" $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\log\$($date)_Bootstrap.log" $idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' +$supportedTypes = 'Backup', 'EpAgentBackup' # Start logging to file Start-Logging -Path $logFile @@ -54,6 +55,12 @@ $sessionId = ([regex]::Matches($parentCmd, $idRegex)).Value[1] # At time of writing, there is no alternative way to discover the job time. $job = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.Id.Guid -eq $jobId} +# Quit if job type is not supported. +If ($job.JobType -notin $supportedTypes) { + Write-LogMessage -Tag 'ERROR' -Message "Job type '$($job.JobType)' is not supported." + Exit 1 +} + # Get the session information and name. $sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $job.JobType $jobName = $sessionInfo.JobName @@ -81,7 +88,7 @@ If ($config.debug_log) { Else { $logJobName = $jobName } - Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)_Bootstrap_$($logJobName).log" + Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)_Bootstrap-$($logJobName).log" } Catch { Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" From ccafdfe46c052dd2495e1c6e8b862a79c73a70d7 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 27 Sep 2021 01:33:20 +0100 Subject: [PATCH 133/175] perf(alertsender): Comment currently unused code --- AlertSender.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 2d4fae3..94380d1 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -28,7 +28,7 @@ If ($Config.debug_log) { } ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') - $logFile = "$PSScriptRoot\log\$($date)_AlertSender_$($logJobName).log" + $logFile = "$PSScriptRoot\log\$($date)_AlertSender-$($logJobName).log" ## Start logging to file Start-Logging $logFile } @@ -119,6 +119,7 @@ if ($jobType -eq 'Backup') { $speedRound = 'Unknown' } + <# TODO: utilise this. # Get objects in session. $sessionObjects = $session.GetTaskSessions() @@ -138,6 +139,7 @@ if ($jobType -eq 'Backup') { $sessionObjectFails++ } } + #> # Add session information to fieldArray. $fieldArray = @( @@ -173,6 +175,7 @@ if ($jobType -eq 'Backup') { } ) + <# TODO: utilise this. # Add object warns/fails to fieldArray if any. If ($sessionObjectWarns -gt 0) { $fieldArray += @( @@ -192,6 +195,7 @@ if ($jobType -eq 'Backup') { } ) } + #> } # If agent backup, gather and include session info. @@ -295,7 +299,7 @@ If ($jobType -eq 'EpAgentBackup') { $fieldArray += @( [PSCustomObject]@{ name = 'Notice' - value = "All useful information has been provided; further details are missing due to limitations in Veeam's PowerShell module." + value = "Further details are missing due to limitations in Veeam's PowerShell module." inline = 'false' } ) From 6a1607a6836c075234795650ed90f95d6ea6cc5b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 28 Sep 2021 02:04:16 +0100 Subject: [PATCH 134/175] fix(vbrsessioninfo): Include session type in name --- resources/VBRSessionInfo.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index 2bb84e4..091d81c 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -19,7 +19,7 @@ Function Get-VBRSessionInfo { $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $SessionId} # Get the job's name from the session details. - $jobName = $session.OrigJobName + $jobName = $session.Name } # Agent job From d94e420cb57cf8cb2af44c4e1a21cdd2c75aa4ce Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 28 Sep 2021 02:08:34 +0100 Subject: [PATCH 135/175] ci(pssa): fix switch --- .github/scripts/pssa.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/pssa.ps1 b/.github/scripts/pssa.ps1 index 0f0e8f8..f04b424 100644 --- a/.github/scripts/pssa.ps1 +++ b/.github/scripts/pssa.ps1 @@ -14,7 +14,7 @@ $issues = foreach ($i in $psFiles.FullName) { $errors = $warnings = $infos = $unknowns = 0 foreach ($i in $issues) { - switch ($issue.Severity) { + switch ($i.Severity) { {$_ -eq 'Error' -or $_ -eq 'ParseError'} { Write-Output "::error file=$($i.ScriptName),line=$($i.Line),col=$($i.Column)::$($i.RuleName) - $($i.Message)" $errors++ From 356dd5ef7d19eb2218de21717f6a6c8573158012 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 28 Sep 2021 03:01:00 +0100 Subject: [PATCH 136/175] fix(deployveeam): Script file names --- resources/DeployVeeamConfiguration.ps1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index b2521eb..bfd394a 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -14,6 +14,9 @@ function DeploymentError { } } +# Import Veeam module +Import-Module Veeam.Backup.PowerShell -DisableNameChecking + # Get all supported jobs $vbrJobs = Get-VBRJob -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob} @@ -25,7 +28,7 @@ if ($vbrJobs.Count -eq 0) { } # Post-job script for Discord notifications -$newPostScriptCmd = 'Powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\DiscordNotificationBootstrap.ps1' +$newPostScriptCmd = 'Powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\Bootstrap.ps1' # Run foreach loop for all found jobs foreach ($job in $vbrJobs) { @@ -35,7 +38,7 @@ foreach ($job in $vbrJobs) { $postScriptCmd = $jobOptions.JobScriptCommand.PostScriptCommandLine # Check if job is already configured with correct post-job script - if ($postScriptCmd.EndsWith('DiscordNotificationBootstrap.ps1') -or $postScriptCmd.EndsWith("DiscordNotificationBootstrap.ps1'")) { + if ($postScriptCmd.EndsWith('Bootstrap.ps1') -or $postScriptCmd.EndsWith("Bootstrap.ps1'")) { Write-Output "`nJob '$($job.Name)' is already configured for Discord notifications; Skipping." Continue } @@ -51,12 +54,14 @@ foreach ($job in $vbrJobs) { until ($overWriteCurrentCmd -eq 'Y' -or $overWriteCurrentCmd -eq 'N') switch ($overWriteCurrentCmd) { + # Default action will be to skip the job. default { Write-Output "`nSkipping job '$($job.Name)'`n"} Y { try { # Check to see if the script has even changed if ($postScriptCmd -ne $newPostScriptCmd) { + # Script is not the same. Update the script command line. $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null From 32bde44ba304144372bb61c839b44ef5e212604b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 28 Sep 2021 03:01:17 +0100 Subject: [PATCH 137/175] feat(deployveeam): Inform user about agent jobs --- resources/DeployVeeamConfiguration.ps1 | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index bfd394a..7ed59de 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -18,10 +18,10 @@ function DeploymentError { Import-Module Veeam.Backup.PowerShell -DisableNameChecking # Get all supported jobs -$vbrJobs = Get-VBRJob -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob} +$backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob} # Make sure we found some jobs -if ($vbrJobs.Count -eq 0) { +if ($backupJobs.Count -eq 0) { Write-Output 'No supported jobs found; Exiting.' Start-Sleep 10 exit @@ -30,8 +30,8 @@ if ($vbrJobs.Count -eq 0) { # Post-job script for Discord notifications $newPostScriptCmd = 'Powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\Bootstrap.ps1' -# Run foreach loop for all found jobs -foreach ($job in $vbrJobs) { +# Run foreach loop for all found backup jobs +foreach ($job in $backupJobs) { # Get post-job script options for job $jobOptions = $job.GetOptions() $postScriptEnabled = $jobOptions.JobScriptCommand.PostScriptEnabled @@ -106,6 +106,19 @@ foreach ($job in $vbrJobs) { } } +# Inform user about agent jobs +$agentJobCount = (Get-VBRComputerBackupJob | Where-Object {$_.Mode -eq 'ManagedByBackupServer'}).Count +If ($agentJobCount -gt 0) { + If ($agentJobCount -eq 1) { + Write-Output "`n$($agentJobCount) agent job has been found." + } + Else { + Write-Output "`n$($agentJobCount) agent jobs have been found." + } + Write-Output "Unfortunately they cannot be configured automatically due to limitations in Veeam's PowerShell module." + Write-Output 'Please manually configure them to receive Discord notifications.' +} + Write-Output "`n`Finished. Exiting." Start-Sleep 10 exit From ac178a4f602b4ab998a869cac99181471eb7b8fa Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Tue, 28 Sep 2021 03:01:43 +0100 Subject: [PATCH 138/175] style(logger): use single quotes for constant --- resources/Logger.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index b250d01..58d0def 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -8,7 +8,7 @@ Function Write-LogMessage { $Tag, $Message ) - $time = Get-Date -Format "HH:mm:ss.ff" + $time = Get-Date -Format 'HH:mm:ss.ff' If ($PSCmdlet.ShouldProcess('Output stream', 'Write log message')) { Write-Output "[$time] $($Tag.ToUpper()): $Message" } From fda54cf3ee4f8b4968a1aab247afe7dacba31f0d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Thu, 30 Sep 2021 19:19:15 +0100 Subject: [PATCH 139/175] docs(readme): Update credits --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b81bd5..c64bc87 100644 --- a/README.md +++ b/README.md @@ -43,8 +43,10 @@ Configuration is set in ./config/conf.json ## Credits -[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is (now loosely) based. +[MelonSmasher](https://github.com/MelonSmasher)//[TheSageColleges](https://github.com/TheSageColleges) for [the project](https://github.com/TheSageColleges/VeeamSlackNotifications) on which this is (now loosely) based. +[dantho281](https://github.com/dantho281) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration for and first works on the `Updater.ps1` script. +[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the [first revision](https://pastebin.com/srN5CKty) of the `ConvertTo-ByteUnit` function. +[philenst](https://github.com/philenst) for the `DeployVeeamConfiguration.ps1` script. +[s0yun](https://github.com/s0yun) for the `Installer.ps1` script. -[dannyt66](https://github.com/dannyt66) for various things - Assistance with silly little issues, the odd bugfix here and there, and the inspiration for and first works on the `Updater.ps1` script. - -[Lee_Dailey](https://reddit.com/u/Lee_Dailey) for general pointers and the first revision of the [`ConvertTo-ByteUnit` function](https://pastebin.com/srN5CKty). +Thank you all. From 82525c98e044dad93a4026388b9d12fa1957d3c1 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Fri, 15 Oct 2021 12:37:39 +0100 Subject: [PATCH 140/175] feat(alertsender): Use Discord timestamps for job times --- AlertSender.ps1 | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 94380d1..f1c86df 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -235,19 +235,7 @@ If ($jobType -eq 'EpAgentBackup') { # Job timings -## Create array of job timings. -# Necessary because $jobEndTime and $jobStartTime are readonly. -# Create writeable object using their values, prepending 0 to single-digit values. -$jobTimes = [PSCustomObject]@{ - StartHour = $jobStartTime.Hour.ToString('00') - StartMinute = $jobStartTime.Minute.ToString('00') - StartSecond = $jobStartTime.Second.ToString('00') - EndHour = $jobEndTime.Hour.ToString('00') - EndMinute = $jobEndTime.Minute.ToString('00') - EndSecond = $jobEndTime.Second.ToString('00') -} - -# Calculate difference between job start and end time. +## Calculate difference between job start and end time. $duration = $jobEndTime - $jobStartTime ## Switch for job duration; define pretty output. @@ -273,7 +261,6 @@ Switch ($duration) { } } - ## Add job times to fieldArray. $fieldArray += @( [PSCustomObject]@{ @@ -283,12 +270,12 @@ $fieldArray += @( } [PSCustomObject]@{ name = 'Time Started' - value = '{0}:{1}:{2}' -f $jobTimes.StartHour, $jobTimes.StartMinute, $jobTimes.StartSecond + value = "" inline = 'true' } [PSCustomObject]@{ name = 'Time Ended' - value = '{0}:{1}:{2}' -f $jobTimes.EndHour, $jobTimes.EndMinute, $jobTimes.EndSecond + value = "" inline = 'true' } ) From ddcce26feae831271fa4d26e1cd7586908c6d860 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sat, 18 Dec 2021 20:02:15 +0000 Subject: [PATCH 141/175] fix(alertsender): incorrect type error --- AlertSender.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index f1c86df..4f5faa1 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -112,7 +112,7 @@ if ($jobType -eq 'Backup') { # Convert bytes to closest unit. $jobSizeRound = ConvertTo-ByteUnit -Data $jobSize $transferSizeRound = ConvertTo-ByteUnit -Data $transferSize - $speedRound = (ConvertTo-ByteUnit -Data $speed) + '/s' + $speedRound = (ConvertTo-ByteUnit -Data $speed).ToString() + '/s' # Set processing speed "Unknown" if 0B/s to avoid confusion. If ($speedRound -eq '0 B/s') { @@ -210,7 +210,7 @@ If ($jobType -eq 'EpAgentBackup') { # Convert bytes to closest unit. $jobProcessedSizeRound = ConvertTo-ByteUnit -Data $jobProcessedSize $jobTransferredSizeRound = ConvertTo-ByteUnit -Data $jobTransferredSize - $speedRound = (ConvertTo-ByteUnit -Data $speed) + '/s' + $speedRound = (ConvertTo-ByteUnit -Data $speed).ToString() + '/s' # Add session information to fieldArray. $fieldArray = @( From 00ddfca17064991270ef0842a37e25b2b455d05d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:37:35 +0000 Subject: [PATCH 142/175] refactor(logging): Add `append` switch parameter --- AlertSender.ps1 | 2 +- resources/Logger.psm1 | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 4f5faa1..4b248e8 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -30,7 +30,7 @@ If ($Config.debug_log) { $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\log\$($date)_AlertSender-$($logJobName).log" ## Start logging to file - Start-Logging $logFile + Start-Logging -Path $logFile -Append } diff --git a/resources/Logger.psm1 b/resources/Logger.psm1 index 58d0def..6a5e54a 100644 --- a/resources/Logger.psm1 +++ b/resources/Logger.psm1 @@ -1,4 +1,4 @@ -# This function logs messages with a type tag +# This function log messages with a type tag Function Write-LogMessage { [CmdletBinding( SupportsShouldProcess, @@ -14,7 +14,7 @@ Function Write-LogMessage { } } -# These functions handles Logging +# These functions handle Logging Function Start-Logging { [CmdletBinding( SupportsShouldProcess, @@ -22,7 +22,9 @@ Function Start-Logging { )] Param( [Parameter(Mandatory)] - $Path + $Path, + [Switch] + $Append ) If ($PSCmdlet.ShouldProcess($Path, 'Start-Transcript')) { Try { @@ -34,6 +36,7 @@ Function Start-Logging { } } } + Function Stop-Logging { [CmdletBinding( SupportsShouldProcess, From c117ed98cbbec4a4411daec6d4270f898ef4fd62 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:53:53 +0000 Subject: [PATCH 143/175] feat(fileislocked): function to detect file lock --- resources/Test-FileIsLocked.psm1 | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 resources/Test-FileIsLocked.psm1 diff --git a/resources/Test-FileIsLocked.psm1 b/resources/Test-FileIsLocked.psm1 new file mode 100644 index 0000000..81f4741 --- /dev/null +++ b/resources/Test-FileIsLocked.psm1 @@ -0,0 +1,33 @@ +Function Test-FileIsLocked { + [cmdletbinding()] + Param ( + [parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)] + [Alias('FullName','PSPath')] + [string[]]$Path + ) + Process { + ForEach ($Item in $Path) { + #Ensure this is a full path + $Item = Convert-Path $Item + #Verify that this is a file and not a directory + If ([System.IO.File]::Exists($Item)) { + Try { + $FileStream = [System.IO.File]::Open($Item,'Open','Write') + $FileStream.Close() + $FileStream.Dispose() + $IsLocked = $False + } + Catch [System.UnauthorizedAccessException] { + $IsLocked = 'AccessDenied' + } + Catch { + $IsLocked = $True + } + [pscustomobject]@{ + File = $Item + IsLocked = $IsLocked + } + } + } + } +} From ce1f594cc9775e7099406bc018545aff61d6d357 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 15:54:37 +0000 Subject: [PATCH 144/175] refactor(logging): combine log files per job --- AlertSender.ps1 | 12 +++++++++++- Bootstrap.ps1 | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 4b248e8..b20e738 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -15,6 +15,7 @@ Import-Module "$PSScriptRoot\resources\Logger.psm1" Import-Module "$PSScriptRoot\resources\ConvertTo-ByteUnit.psm1" Import-Module "$PSScriptRoot\resources\VBRSessionInfo.psm1" Import-Module "$PSScriptRoot\resources\UpdateInfo.psm1" +Import-Module "$PSScriptRoot\resources\Test-FileIsLocked.psm1" # Start logging if logging is enabled in config @@ -26,9 +27,18 @@ If ($Config.debug_log) { Else { $logJobName = $jobName } + ## Set log file name $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') - $logFile = "$PSScriptRoot\log\$($date)_AlertSender-$($logJobName).log" + $logFile = "$PSScriptRoot\log\$($date)-$($logJobName).log" + + ## Wait until log file is closed by Bootstrap.ps1 + do { + $logLocked = $(Test-FileIsLocked -Path "$logFile.log").IsLocked + Start-Sleep -Seconds 1 + } + until (-not $logLocked) + ## Start logging to file Start-Logging -Path $logFile -Append } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 0077ec0..fc76687 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -88,7 +88,7 @@ If ($config.debug_log) { Else { $logJobName = $jobName } - Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)_Bootstrap-$($logJobName).log" + Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)-$($logJobName).log" } Catch { Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" From 41161d532b48a8aec86d5c7948ef73195d450e5d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:55:14 +0000 Subject: [PATCH 145/175] feat: add replica support --- AlertSender.ps1 | 20 +++++++++++++------- Bootstrap.ps1 | 2 +- resources/DeployVeeamConfiguration.ps1 | 23 +++++++++++++---------- resources/VBRSessionInfo.psm1 | 2 +- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index b20e738..556b3f8 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -111,7 +111,7 @@ If ($session.State -ne 'Stopped') { # Define session statistics for the report. ## If VM backup, gather and include session info. -if ($jobType -eq 'Backup') { +if ($jobType -in 'Backup','Replica') { # Gather session data sizes and timing. [Float]$jobSize = $session.BackupStats.DataSize [Float]$transferSize = $session.BackupStats.BackupSize @@ -302,14 +302,20 @@ If ($jobType -eq 'EpAgentBackup') { ) } +# Define nice job type name +Switch ($jobType) { + Backup {$jobTypeNice = 'Backup'} + Replica {$jobTypeNice = 'Replication'} + EpAgentBackup {$jobTypeNice = 'Agent Backup'} +} # Switch for the session status to decide the embed colour. Switch ($status) { - None {$colour = '16777215'} - Warning {$colour = '16776960'} - Success {$colour = '65280'} - Failed {$colour = '16711680'} - Default {$colour = '16777215'} + None {$colour = '16777215'} + Warning {$colour = '16776960'} + Success {$colour = '65280'} + Failed {$colour = '16711680'} + Default {$colour = '16777215'} } # Decide whether to mention user @@ -338,7 +344,7 @@ Catch { $embedArray = @( [PSCustomObject]@{ title = $jobName - description = $status + description = "$status ($jobTypeNice)" color = $colour thumbnail = $thumbObject fields = $fieldArray diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index fc76687..28c607a 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -8,7 +8,7 @@ $configFile = "$PSScriptRoot\config\conf.json" $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') $logFile = "$PSScriptRoot\log\$($date)_Bootstrap.log" $idRegex = '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}' -$supportedTypes = 'Backup', 'EpAgentBackup' +$supportedTypes = 'Backup', 'EpAgentBackup','Replica' # Start logging to file Start-Logging -Path $logFile diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index 7ed59de..ed855dc 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -18,7 +18,7 @@ function DeploymentError { Import-Module Veeam.Backup.PowerShell -DisableNameChecking # Get all supported jobs -$backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob} +$backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob -or $_.IsReplica} # Make sure we found some jobs if ($backupJobs.Count -eq 0) { @@ -27,6 +27,9 @@ if ($backupJobs.Count -eq 0) { exit } +# Set name +$jobNameType = "'$($job.Name)' (type '$($job.JobType)')" + # Post-job script for Discord notifications $newPostScriptCmd = 'Powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\Bootstrap.ps1' @@ -39,13 +42,13 @@ foreach ($job in $backupJobs) { # Check if job is already configured with correct post-job script if ($postScriptCmd.EndsWith('Bootstrap.ps1') -or $postScriptCmd.EndsWith("Bootstrap.ps1'")) { - Write-Output "`nJob '$($job.Name)' is already configured for Discord notifications; Skipping." + Write-Output "`nJob $jobNameType is already configured for Discord notifications; Skipping." Continue } # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. if ($postScriptEnabled) { - Write-Output "`nJob '$($job.Name)' has an existing post-job script.`nScript: $postScriptCmd" + Write-Output "`nJob $jobNameType has an existing post-job script.`nScript: $postScriptCmd" Write-Output "`nIf you wish to receive Discord notifications for this job, you must overwrite the existing post-job script." do { @@ -56,7 +59,7 @@ foreach ($job in $backupJobs) { switch ($overWriteCurrentCmd) { # Default action will be to skip the job. - default { Write-Output "`nSkipping job '$($job.Name)'`n"} + default { Write-Output "`nSkipping job $jobNameType`n"} Y { try { # Check to see if the script has even changed @@ -66,12 +69,12 @@ foreach ($job in $backupJobs) { $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - Write-Output "Updated post-job script for job '$($job.Name)'.`nOld: $postScriptCmd`nNew: $newPostScriptCmd" - Write-Output "Job '$($job.Name)' is now configured for Discord notifications." + Write-Output "Updated post-job script for job $jobNameType.`nOld: $postScriptCmd`nNew: $newPostScriptCmd" + Write-Output "Job $jobNameType is now configured for Discord notifications." } else { # Script hasn't changed. Notify user of this and continue. - Write-Output "Job '$($job.Name)' is already configured for Discord notifications; Skipping." + Write-Output "Job $jobNameType is already configured for Discord notifications; Skipping." } } catch { @@ -82,13 +85,13 @@ foreach ($job in $backupJobs) { } else { do { - $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for job '$($job.Name)'? Y/N" + $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for job $jobNameType? Y/N" } until ($setNewPostScript -eq 'Y' -or $setNewPostScript -eq 'N') Switch ($setNewPostScript) { # Default action will be to skip the job. - default { Write-Output "Skipping job '$($job.Name)'`n"} + default { Write-Output "Skipping job $jobNameType`n"} Y { try { # Sets post-job script to Enabled and sets the command line to full command including path. @@ -96,7 +99,7 @@ foreach ($job in $backupJobs) { $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - Write-Output "Job '$($job.Name)' is now configured for Discord notifications." + Write-Output "Job $jobNameType is now configured for Discord notifications." } catch { DeploymentError diff --git a/resources/VBRSessionInfo.psm1 b/resources/VBRSessionInfo.psm1 index 091d81c..9fa53e9 100644 --- a/resources/VBRSessionInfo.psm1 +++ b/resources/VBRSessionInfo.psm1 @@ -13,7 +13,7 @@ Function Get-VBRSessionInfo { Switch ($JobType) { # VM job - {$_ -eq 'Backup'} { + {$_ -in 'Backup','Replica'} { # Get the session details. $session = Get-VBRBackupSession | Where-Object {$_.Id.Guid -eq $SessionId} From d42495a6eec22bfef5d2a8bc5b077ef18beef2db Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:05:41 +0000 Subject: [PATCH 146/175] refactor: improve logging setup --- AlertSender.ps1 | 19 ++++--------------- Bootstrap.ps1 | 25 ++++++++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 556b3f8..fd46f84 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -3,7 +3,8 @@ Param( [String]$jobName, [String]$id, [String]$jobType, - $Config + $Config, + $Logfile ) # Convert config from JSON @@ -20,27 +21,15 @@ Import-Module "$PSScriptRoot\resources\Test-FileIsLocked.psm1" # Start logging if logging is enabled in config If ($Config.debug_log) { - ## Replace spaces if any in the job name - If ($jobName -match ' ') { - $logJobName = $jobName.Replace(' ', '_') - } - Else { - $logJobName = $jobName - } - - ## Set log file name - $date = (Get-Date -UFormat %Y-%m-%d_%T).Replace(':','.') - $logFile = "$PSScriptRoot\log\$($date)-$($logJobName).log" - ## Wait until log file is closed by Bootstrap.ps1 do { - $logLocked = $(Test-FileIsLocked -Path "$logFile.log").IsLocked + $logLocked = $(Test-FileIsLocked -Path "$Logfile").IsLocked Start-Sleep -Seconds 1 } until (-not $logLocked) ## Start logging to file - Start-Logging -Path $logFile -Append + Start-Logging -Path $Logfile -Append } diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 28c607a..22ca82e 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -18,7 +18,7 @@ Write-LogMessage -Tag 'INFO' -Message "Version: $(Get-Content "$PSScriptRoot\res # Retrieve configuration. ## Pull config to PSCustomObject -$config = Get-Content -Raw $configFile | ConvertFrom-Json +$config = Get-Content -Raw $configFile | ConvertFrom-Json # TODO: import config from param instead of later as file. Can then improve logging flow. # Stop logging and remove log file if logging is disable in config. If (-not $config.debug_log) { @@ -65,11 +65,21 @@ If ($job.JobType -notin $supportedTypes) { $sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $job.JobType $jobName = $sessionInfo.JobName -Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId)." +Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId) - Session & job detection complete." + +# Set log file name based on job +## Replace spaces if any in the job name +If ($jobName -match ' ') { + $logJobName = $jobName.Replace(' ', '_') +} +Else { + $logJobName = $jobName +} +$newLogfile = "$PSScriptRoot\log\$($date)-$($logJobName).log" # Build argument string for the alert sender script. $powershellArguments = "-file $PSScriptRoot\AlertSender.ps1", "-JobName `"$jobName`"", "-Id `"$sessionId`"","-JobType `"$($job.JobType)`"", ` - "-Config `"$($configRaw)`"" + "-Config `"$($configRaw)`"", "-Logfile `"$newLogfile`"" # Start a new new script in a new process with some of the information gathered here. # This allows Veeam to finish the current session faster and allows us gather information from the completed job. @@ -81,14 +91,7 @@ If ($config.debug_log) { # Rename log file to include the job name. Try { - ## Replace spaces if any in the job name - If ($jobName -match ' ') { - $logJobName = $jobName.Replace(' ', '_') - } - Else { - $logJobName = $jobName - } - Rename-Item -Path $logFile -NewName "$PSScriptRoot\log\$($date)-$($logJobName).log" + Rename-Item -Path $logFile -NewName "$newLogfile" } Catch { Write-LogMessage -Tag 'ERROR' -Message "Failed to rename log file: $_" From cf7f92465be6fddf17752377c9ba766f1b211f5f Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 21:50:48 +0000 Subject: [PATCH 147/175] style(embed): job type detail improvement --- AlertSender.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index fd46f84..3fe0181 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -293,8 +293,8 @@ If ($jobType -eq 'EpAgentBackup') { # Define nice job type name Switch ($jobType) { - Backup {$jobTypeNice = 'Backup'} - Replica {$jobTypeNice = 'Replication'} + Backup {$jobTypeNice = 'VM Backup'} + Replica {$jobTypeNice = 'VM Replication'} EpAgentBackup {$jobTypeNice = 'Agent Backup'} } From 5b2cd639824100809b931b3ea74acf82341569a5 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 21:51:40 +0000 Subject: [PATCH 148/175] style(embed): description formatting improvement --- AlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 3fe0181..785e98f 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -333,7 +333,7 @@ Catch { $embedArray = @( [PSCustomObject]@{ title = $jobName - description = "$status ($jobTypeNice)" + description = "Session result: $status\nJob type: $jobTypeNice" color = $colour thumbnail = $thumbObject fields = $fieldArray From 356bd42c54cb1aaf69689c3987faa8298a19a045 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 22:03:05 +0000 Subject: [PATCH 149/175] fix(installer): PSUseConsistentIndentation --- Installer.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installer.ps1 b/Installer.ps1 index 7860bc8..4921793 100644 --- a/Installer.ps1 +++ b/Installer.ps1 @@ -8,7 +8,7 @@ $webhookRegex = 'https:\/\/(.*\.)?discord[app]?.com\/api\/webhooks\/([^\/]+)\/([ # Get latest release from GitHub [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $latestVersion = ((Invoke-WebRequest -Uri "https://github.com/tigattack/$project/releases/latest" ` - -Headers @{'Accept'='application/json'} -UseBasicParsing).Content | ConvertFrom-Json).tag_name + -Headers @{'Accept'='application/json'} -UseBasicParsing).Content | ConvertFrom-Json).tag_name # Check if this project is already installed and, if so, whether it's the latest version. if (Test-Path $rootPath\$project) { From 8d15bdd5e0e5906c2a7be4df2ab8b8afac74fa07 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Sun, 19 Dec 2021 22:03:16 +0000 Subject: [PATCH 150/175] fix(pssa-settings): PSUseConsistentIndentation --- .github/scripts/pssa-settings.psd1 | 26 +++++++++++++------------- .github/scripts/pssa.ps1 | 3 ++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/scripts/pssa-settings.psd1 b/.github/scripts/pssa-settings.psd1 index 54e3295..0b3173d 100644 --- a/.github/scripts/pssa-settings.psd1 +++ b/.github/scripts/pssa-settings.psd1 @@ -35,17 +35,17 @@ ) } PSPlaceCloseBrace = @{ - Enable = $true - NoEmptyLineBefore = $false - IgnoreOneLineBlock = $true - NewLineAfter = $true - } + Enable = $true + NoEmptyLineBefore = $false + IgnoreOneLineBlock = $true + NewLineAfter = $true + } PSPlaceOpenBrace = @{ - Enable = $true - OnSameLine = $true - NewLineAfter = $true - IgnoreOneLineBlock = $true - } + Enable = $true + OnSameLine = $true + NewLineAfter = $true + IgnoreOneLineBlock = $true + } PSUseConsistentIndentation = @{ Enable = $true IndentationSize = 4 @@ -53,8 +53,8 @@ Kind = 'tab' } PSAvoidLongLines = @{ - Enable = $true - MaximumLineLength = 155 - } + Enable = $true + MaximumLineLength = 155 + } } } diff --git a/.github/scripts/pssa.ps1 b/.github/scripts/pssa.ps1 index f04b424..ce64f8f 100644 --- a/.github/scripts/pssa.ps1 +++ b/.github/scripts/pssa.ps1 @@ -10,9 +10,10 @@ $issues = foreach ($i in $psFiles.FullName) { Invoke-ScriptAnalyzer -Path $i -Recurse -Settings ./.github/scripts/pssa-settings.psd1 } -# Get results, types and report to GitHub Actions +# init and set variables $errors = $warnings = $infos = $unknowns = 0 +# Get results, types and report to GitHub Actions foreach ($i in $issues) { switch ($i.Severity) { {$_ -eq 'Error' -or $_ -eq 'ParseError'} { From 902f5c87af22eb7a05d1c40e1a85bb9aba3c74c1 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:08:04 +0000 Subject: [PATCH 151/175] feat: remove version phrases; plain status instead --- AlertSender.ps1 | 24 +++++++++-------- resources/UpdateInfo.psm1 | 50 +++++++++++------------------------ resources/VersionPhrases.json | 23 ---------------- 3 files changed, 28 insertions(+), 69 deletions(-) delete mode 100644 resources/VersionPhrases.json diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 785e98f..02d7b8c 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -44,10 +44,18 @@ $updateStatus = Get-UpdateStatus # Define static output objects. -## Get and define update status message. -$footerAddition = (Get-UpdateMessage -CurrentVersion $updateStatus.CurrentVersion -LatestVersion $updateStatus.LatestVersion) -$footerAddition = $footerAddition.Replace('latestVerPlaceholder',"$($updateStatus.LatestVersion)") -$footerAddition = $footerAddition.Replace('currentVerPlaceholder',"$($updateStatus.CurrentVersion)") +## Footer message. +Switch ($updateStatus) { + Current { $footerAddition = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Up to date."} + Behind { $footerAddition = "- Update to $($updateStatus.LatestVersion) is available!"} + Ahead { $footerAddition = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Pre-release."} +} + +## Footer object. +$footerObject = [PSCustomObject]@{ + text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) $footerAddition" + icon_url = 'https://avatars0.githubusercontent.com/u/10629864' +} ## Define thumbnail object. If ($Config.thumbnail) { @@ -61,12 +69,6 @@ Else { } } -## Define footer object. -$footerObject = [PSCustomObject]@{ - text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion). $footerAddition" - icon_url = 'https://avatars0.githubusercontent.com/u/10629864' -} - # Job info preparation @@ -333,7 +335,7 @@ Catch { $embedArray = @( [PSCustomObject]@{ title = $jobName - description = "Session result: $status\nJob type: $jobTypeNice" + description = "Session result: $status`nJob type: $jobTypeNice" color = $colour thumbnail = $thumbObject fields = $fieldArray diff --git a/resources/UpdateInfo.psm1 b/resources/UpdateInfo.psm1 index 057c90e..a95febd 100644 --- a/resources/UpdateInfo.psm1 +++ b/resources/UpdateInfo.psm1 @@ -12,44 +12,24 @@ function Get-UpdateStatus { # Release IDs are returned in a format of {"id":3622206,"tag_name":"v1.0"}, so we need to extract tag_name. $latestVersion = ConvertFrom-Json $latestRelease.Content | ForEach-Object {$_.tag_name} - # Create PSObject to return. - New-Object PSObject -Property @{ - CurrentVersion = $currentVersion - LatestVersion = $latestVersion + If ($currentVersion -gt $latestVersion) { + $status = 'Ahead' + } + elseif ($currentVersion -lt $latestVersion) { + $status = 'Behind' + } + else { + $status = 'Current' } - } -} - -function Get-UpdateMessage { - Param ( - [Parameter(Mandatory)] - $CurrentVersion, - [Parameter(Mandatory)] - $LatestVersion - ) - - process { - - # Get version announcement phrases. - $phrases = Get-Content -Raw "$PSScriptRoot\VersionPhrases.json" | ConvertFrom-Json - - # Comparing local and latest versions and determine if an update is required, then use that information to build the footer text. - # Picks a phrase at random from the list above for the version statement in the footer of the backup report. - Switch ($CurrentVersion) { - {$_ -lt $LatestVersion} { - $updateMessage = (Get-Random -InputObject $phrases.older -Count 1) - } - - {$_ -eq $LatestVersion} { - $updateMessage = (Get-Random -InputObject $phrases.current -Count 1) - } - {$_ -gt $LatestVersion} { - $updateMessage = (Get-Random -InputObject $phrases.newer -Count 1) - } + # Create PSObject to return. + $out = New-Object PSObject -Property @{ + CurrentVersion = $currentVersion + LatestVersion = $latestVersion + Status = $status } - # Return update message. - $updateMessage + # Return PSObject. + return $out } } diff --git a/resources/VersionPhrases.json b/resources/VersionPhrases.json deleted file mode 100644 index 3daf5b5..0000000 --- a/resources/VersionPhrases.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "older": [ - "Jesus mate, you're out of date! Latest is latestVerPlaceholder.", - "Bloody hell you muppet, you need to update! Latest is latestVerPlaceholder.", - "Fuck me sideways, you're out of date! Latest is latestVerPlaceholder.", - "Shitting heck lad, you need to update! Latest is latestVerPlaceholder.", - "Christ almighty, you're out of date! Latest is latestVerPlaceholder." - ], - "current": [ - "Nice work mate, you're up to date.", - "Good shit buddy, you're up to date.", - "Top stuff my dude, you're running the latest version.", - "Good job fam, you're all up to date.", - "Lovely stuff mate, you're running the latest version." - ], - "newer": [ - "Wewlad, check you out running a pre-release version, latest is latestVerPlaceholder!", - "Christ m8e, this is mental, you're ahead of release, latest is latestVerPlaceholder!", - "You nutter, you're running a pre-release version! Latest is latestVerPlaceholder!", - "Bloody hell mate, this is unheard of, currentVerPlaceholder isn't even released yet, latest is latestVerPlaceholder!", - "Fuuuckin hell, currentVerPlaceholder hasn't even been released! Latest is latestVerPlaceholder." - ] -} From e2ebc64383147a23d5c10a1e5a7464c92a382921 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:08:42 +0000 Subject: [PATCH 152/175] style(install): minor output improvements --- Installer.ps1 | 2 +- resources/DeployVeeamConfiguration.ps1 | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Installer.ps1 b/Installer.ps1 index 4921793..e3aa216 100644 --- a/Installer.ps1 +++ b/Installer.ps1 @@ -49,7 +49,7 @@ until ($webhookUrl -match $webhookRegex) Write-Output "`nDo you wish to be mentioned in Discord when a job fails or finishes with warnings?" do { - $mentionPreference = Read-Host -Prompt "1 = No`n2 = On warn`n3 = On fail`n4 = 2 and 3`nYour choice" + $mentionPreference = Read-Host -Prompt "1 = No`n2 = On warn`n3 = On fail`n4 = On fail and on warn`nYour choice" If (1..4 -notcontains $mentionPreference) { Write-Output "`nInvalid choice. Please try again." } diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index ed855dc..10530cc 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -17,6 +17,9 @@ function DeploymentError { # Import Veeam module Import-Module Veeam.Backup.PowerShell -DisableNameChecking +# Write notice +Write-Output "`nPlease note: This script can currently only be used to configure VM backup jobs and VM replica jobs.`n" + # Get all supported jobs $backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob -or $_.IsReplica} From d7ede92cd5c1152c6d868d1b2fdfec28eb7c07be Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:09:15 +0000 Subject: [PATCH 153/175] docs(readme): add supported job type information --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c64bc87..108b619 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,23 @@ Sends notifications from Veeam Backup & Replication to Discord. If you enjoy this project and would like to help out, please do so. If you're interested in helping out, contact me on Discord - `tigatack#7987` -As much as I love this project, my free time is all but nonexistent and work is needed to add functionality for different types of jobs than just VM backups and to bring this project in-line with recent changes in Veeam Backup & Replication (VBR) and VBR's PowerShell module. +As much as I love this project, free time is hard to find and some work is needed to add functionality for more types of jobs, add more optional detail to outputs, and to bring this project in-line with recent changes in Veeam Backup & Replication (VBR) and VBR's PowerShell module. -This is work that I will inevitably get to at some point, but I'd hate to see people left in the lurch with this and would love to have it done sooner rather than later. +## Supported Job Types + +* VM Backup +* VM Replication +* Agents managed by backup server + +### Agent job caveats + +Due to limitations caused by the way some types of Veeam Agent jobs are executed, only Agent jobs of type "Managed by backup server" support post-job scripts. +In the Veeam Backup & Replication Console, such jobs will show up with type "Windows/Linux Agent Backup". If you see "Windows/Linux Agent _Policy_", this job is not supported. +In the Veeam Backup & Replication PowerShell module, such jobs will show up with type "EpAgentBackup". If you see "EpAgentPolicy", this job is not supported. +You can read about the difference between these two Agent job types [here](https://helpcenter.veeam.com/docs/backup/agents/agent_job_protection_mode.html?ver=110#selecting-job-mode). + +Unfortunately, even Agent job sessions managed by the backup server, while supported, are limited in data output. + As much relevant information as I've been able to discover from such job sessions is included in the Discord embed, but I welcome any suggestions for improvement in this area. ## Configuration options From 811708d8fe5aef8c8f51362cdf0856ee0c671702 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:12:11 +0000 Subject: [PATCH 154/175] fix(alertsender): fix footer message --- AlertSender.ps1 | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 02d7b8c..2be5595 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -46,14 +46,20 @@ $updateStatus = Get-UpdateStatus ## Footer message. Switch ($updateStatus) { - Current { $footerAddition = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Up to date."} - Behind { $footerAddition = "- Update to $($updateStatus.LatestVersion) is available!"} - Ahead { $footerAddition = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Pre-release."} + Current { + $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Up to date." + } + Behind { + $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Update to $($updateStatus.LatestVersion) is available!" + } + Ahead { + $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Pre-release." + } } ## Footer object. $footerObject = [PSCustomObject]@{ - text = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) $footerAddition" + text = $footerMessage icon_url = 'https://avatars0.githubusercontent.com/u/10629864' } From 63574b0feb28ea6faf1cd6b166c98c839daecff2 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:17:02 +0000 Subject: [PATCH 155/175] docs(readme): add simple detail --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 108b619..2a17bd7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Veeam Backup & Replication Notifications for Discord -Sends notifications from Veeam Backup & Replication to Discord. +Sends notifications from Veeam Backup & Replication to Discord, detailing session results and statistics and optionally alerting you via mention when a job finishes in a warning or failed state. + +![Chat Example](https://github.com/tigattack/VeeamDiscordNotifications/blob/master/asset/example.png) ## Installing @@ -10,8 +12,6 @@ Sends notifications from Veeam Backup & Replication to Discord. * Option 2 - Manual install * Follow the [setup instructions](https://blog.tiga.tech/veeam-b-r-notifications-in-discord/). -![Chat Example](https://github.com/tigattack/VeeamDiscordNotifications/blob/master/asset/example.png) - ### Looking for volunteers If you enjoy this project and would like to help out, please do so. If you're interested in helping out, contact me on Discord - `tigatack#7987` From 3ec2c7c3a77eab053268824d1844eee745a6952b Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:20:46 +0000 Subject: [PATCH 156/175] docs(readme): stylistic changes --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a17bd7..5628b7d 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,10 @@ As much as I love this project, free time is hard to find and some work is neede ### Agent job caveats Due to limitations caused by the way some types of Veeam Agent jobs are executed, only Agent jobs of type "Managed by backup server" support post-job scripts. -In the Veeam Backup & Replication Console, such jobs will show up with type "Windows/Linux Agent Backup". If you see "Windows/Linux Agent _Policy_", this job is not supported. -In the Veeam Backup & Replication PowerShell module, such jobs will show up with type "EpAgentBackup". If you see "EpAgentPolicy", this job is not supported. +Such jobs will show up as follows: +* In Veeam Backup & Replication Console, with type "Windows/Linux Agent Backup". If you see "Windows/Linux Agent _Policy_", this job is not supported. +* In Veeam Backup & Replication PowerShell module, with type "EpAgentBackup". If you see "EpAgentPolicy", this job is not supported. + You can read about the difference between these two Agent job types [here](https://helpcenter.veeam.com/docs/backup/agents/agent_job_protection_mode.html?ver=110#selecting-job-mode). Unfortunately, even Agent job sessions managed by the backup server, while supported, are limited in data output. From f5955226b3ed177e6a1269c74198e9b855f2b637 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 01:25:51 +0000 Subject: [PATCH 157/175] docs(readme): stylistic changes --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5628b7d..df66059 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Veeam Backup & Replication Notifications for Discord -Sends notifications from Veeam Backup & Replication to Discord, detailing session results and statistics and optionally alerting you via mention when a job finishes in a warning or failed state. +Send Veeam Backup & Replication session summary notifications to Discord, detailing session result and statistics and optionally alerting you via mention when a job finishes in a warning or failed state. ![Chat Example](https://github.com/tigattack/VeeamDiscordNotifications/blob/master/asset/example.png) @@ -28,8 +28,10 @@ As much as I love this project, free time is hard to find and some work is neede Due to limitations caused by the way some types of Veeam Agent jobs are executed, only Agent jobs of type "Managed by backup server" support post-job scripts. Such jobs will show up as follows: -* In Veeam Backup & Replication Console, with type "Windows/Linux Agent Backup". If you see "Windows/Linux Agent _Policy_", this job is not supported. -* In Veeam Backup & Replication PowerShell module, with type "EpAgentBackup". If you see "EpAgentPolicy", this job is not supported. +* In Veeam Backup & Replication Console, with type "Windows/Linux Agent Backup". +If you see "Windows/Linux Agent _Policy_", this job is not supported. +* In Veeam Backup & Replication PowerShell module, with type "EpAgentBackup". +If you see "EpAgentPolicy", this job is not supported. You can read about the difference between these two Agent job types [here](https://helpcenter.veeam.com/docs/backup/agents/agent_job_protection_mode.html?ver=110#selecting-job-mode). @@ -38,7 +40,7 @@ Unfortunately, even Agent job sessions managed by the backup server, while suppo ## Configuration options -Configuration is set in ./config/conf.json +Configuration can be found in `C:\VeeamScripts\VeeamDiscordNotifications\config\conf.json` | Name | Type | Required | Default | Description | |--------------------- |-------- |--------- |------------------ | ---------------------------------------------------------------------------------------------------------- | From e273cd646ab81f9063cb3295fbb0609e0911a079 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:10:45 +0000 Subject: [PATCH 158/175] feat(deployconfig): deploy to all supported jobs --- resources/DeployVeeamConfiguration.ps1 | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index 10530cc..bfb1402 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -17,11 +17,10 @@ function DeploymentError { # Import Veeam module Import-Module Veeam.Backup.PowerShell -DisableNameChecking -# Write notice -Write-Output "`nPlease note: This script can currently only be used to configure VM backup jobs and VM replica jobs.`n" - # Get all supported jobs -$backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object {$_.IsBackupJob -or $_.IsReplica} +$backupJobs = Get-VBRJob -WarningAction SilentlyContinue | Where-Object { + $_.JobType -in 'Backup', 'Replica', 'EpAgentBackup' +} # Make sure we found some jobs if ($backupJobs.Count -eq 0) { @@ -112,19 +111,6 @@ foreach ($job in $backupJobs) { } } -# Inform user about agent jobs -$agentJobCount = (Get-VBRComputerBackupJob | Where-Object {$_.Mode -eq 'ManagedByBackupServer'}).Count -If ($agentJobCount -gt 0) { - If ($agentJobCount -eq 1) { - Write-Output "`n$($agentJobCount) agent job has been found." - } - Else { - Write-Output "`n$($agentJobCount) agent jobs have been found." - } - Write-Output "Unfortunately they cannot be configured automatically due to limitations in Veeam's PowerShell module." - Write-Output 'Please manually configure them to receive Discord notifications.' -} - Write-Output "`n`Finished. Exiting." Start-Sleep 10 exit From 11476804394e510088007b2fb57e11fc15928b36 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:13:44 +0000 Subject: [PATCH 159/175] feat(deployconfig): better output detail and style also fix job names not showing in query --- resources/DeployVeeamConfiguration.ps1 | 36 ++++++++++++++------------ 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index bfb1402..27c5ba3 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -14,6 +14,9 @@ function DeploymentError { } } +# Post-job script for Discord notifications +$newPostScriptCmd = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\Bootstrap.ps1' + # Import Veeam module Import-Module Veeam.Backup.PowerShell -DisableNameChecking @@ -28,29 +31,30 @@ if ($backupJobs.Count -eq 0) { Start-Sleep 10 exit } - -# Set name -$jobNameType = "'$($job.Name)' (type '$($job.JobType)')" - -# Post-job script for Discord notifications -$newPostScriptCmd = 'Powershell.exe -ExecutionPolicy Bypass -File C:\VeeamScripts\VeeamDiscordNotifications\Bootstrap.ps1' +else { + Write-Output "Found $($backupJobs.count) supported jobs:" + Format-Table -InputObject $backupJobs -Property Name,@{Name='Type'; Expression={$_.TypeToString}} -AutoSize +} # Run foreach loop for all found backup jobs foreach ($job in $backupJobs) { + # Set name string + $jobName = "`"$($job.Name)`"" + # Get post-job script options for job $jobOptions = $job.GetOptions() $postScriptEnabled = $jobOptions.JobScriptCommand.PostScriptEnabled $postScriptCmd = $jobOptions.JobScriptCommand.PostScriptCommandLine # Check if job is already configured with correct post-job script - if ($postScriptCmd.EndsWith('Bootstrap.ps1') -or $postScriptCmd.EndsWith("Bootstrap.ps1'")) { - Write-Output "`nJob $jobNameType is already configured for Discord notifications; Skipping." + if ($postScriptCmd.EndsWith('\Bootstrap.ps1') -or $postScriptCmd.EndsWith("\Bootstrap.ps1'")) { + Write-Output "`n$($jobName) is already configured for Discord notifications; Skipping." Continue } # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. if ($postScriptEnabled) { - Write-Output "`nJob $jobNameType has an existing post-job script.`nScript: $postScriptCmd" + Write-Output "`n$($jobName) has an existing post-job script.`nScript: $postScriptCmd" Write-Output "`nIf you wish to receive Discord notifications for this job, you must overwrite the existing post-job script." do { @@ -61,7 +65,7 @@ foreach ($job in $backupJobs) { switch ($overWriteCurrentCmd) { # Default action will be to skip the job. - default { Write-Output "`nSkipping job $jobNameType`n"} + default { Write-Output "`nSkipping job $($jobName)`n"} Y { try { # Check to see if the script has even changed @@ -71,12 +75,12 @@ foreach ($job in $backupJobs) { $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - Write-Output "Updated post-job script for job $jobNameType.`nOld: $postScriptCmd`nNew: $newPostScriptCmd" - Write-Output "Job $jobNameType is now configured for Discord notifications." + Write-Output "Updated post-job script for job $($jobName).`nOld: $postScriptCmd`nNew: $newPostScriptCmd" + Write-Output "$($jobName) is now configured for Discord notifications." } else { # Script hasn't changed. Notify user of this and continue. - Write-Output "Job $jobNameType is already configured for Discord notifications; Skipping." + Write-Output "$($jobName) is already configured for Discord notifications; Skipping." } } catch { @@ -87,13 +91,13 @@ foreach ($job in $backupJobs) { } else { do { - $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for job $jobNameType? Y/N" + $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for $($jobName) ($($job.TypeToString))? Y/N" } until ($setNewPostScript -eq 'Y' -or $setNewPostScript -eq 'N') Switch ($setNewPostScript) { # Default action will be to skip the job. - default { Write-Output "Skipping job $jobNameType`n"} + default { Write-Output "Skipping job $($jobName)"} Y { try { # Sets post-job script to Enabled and sets the command line to full command including path. @@ -101,7 +105,7 @@ foreach ($job in $backupJobs) { $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - Write-Output "Job $jobNameType is now configured for Discord notifications." + Write-Output "`n$($jobName) is now configured for Discord notifications." } catch { DeploymentError From 713e39640755705f8b5f30b65878d02982e5480c Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:15:16 +0000 Subject: [PATCH 160/175] feat(deployconfig): reconfigure jobs using old path --- resources/DeployVeeamConfiguration.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index 27c5ba3..dd7d1e6 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -51,6 +51,21 @@ foreach ($job in $backupJobs) { Write-Output "`n$($jobName) is already configured for Discord notifications; Skipping." Continue } + elseif ($postScriptCmd.EndsWith('\DiscordNotificationBootstrap.ps1') -or $postScriptCmd.EndsWith("\DiscordNotificationBootstrap.ps1'")) { + Write-Output "`n$($jobName) is configured for an older version of Discord notifications; Updating..." + try { + # Sets post-job script to Enabled and sets the command line to full command including path. + $jobOptions.JobScriptCommand.PostScriptEnabled = $true + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "$($jobName) is now updated." + } + catch { + DeploymentError + } + Continue + } # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. if ($postScriptEnabled) { From c0a1f338dc2800db168abeeab80cab554ec9697d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:26:55 +0000 Subject: [PATCH 161/175] feat(deployconfig): option to configure all supported jobs --- resources/DeployVeeamConfiguration.ps1 | 239 +++++++++++++++++-------- 1 file changed, 166 insertions(+), 73 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index dd7d1e6..3e8acc4 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -36,97 +36,190 @@ else { Format-Table -InputObject $backupJobs -Property Name,@{Name='Type'; Expression={$_.TypeToString}} -AutoSize } -# Run foreach loop for all found backup jobs -foreach ($job in $backupJobs) { - # Set name string - $jobName = "`"$($job.Name)`"" - - # Get post-job script options for job - $jobOptions = $job.GetOptions() - $postScriptEnabled = $jobOptions.JobScriptCommand.PostScriptEnabled - $postScriptCmd = $jobOptions.JobScriptCommand.PostScriptCommandLine - - # Check if job is already configured with correct post-job script - if ($postScriptCmd.EndsWith('\Bootstrap.ps1') -or $postScriptCmd.EndsWith("\Bootstrap.ps1'")) { - Write-Output "`n$($jobName) is already configured for Discord notifications; Skipping." - Continue - } - elseif ($postScriptCmd.EndsWith('\DiscordNotificationBootstrap.ps1') -or $postScriptCmd.EndsWith("\DiscordNotificationBootstrap.ps1'")) { - Write-Output "`n$($jobName) is configured for an older version of Discord notifications; Updating..." - try { - # Sets post-job script to Enabled and sets the command line to full command including path. - $jobOptions.JobScriptCommand.PostScriptEnabled = $true - $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd - Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - - Write-Output "$($jobName) is now updated." +# Query configure all or selected jobs +do { + $configChoice = Read-Host -Prompt 'Do you wish to configure all supported jobs or make a choice for each job? A(ll)/S(elected)' +} +until ($configChoice -in 'A', 'All', 'S', 'Selected') + +If ($configChoice -in 'S', 'Selected') { + # Run foreach loop for all found backup jobs + foreach ($job in $backupJobs) { + # Set name string + $jobName = "`"$($job.Name)`"" + + # Get post-job script options for job + $jobOptions = $job.GetOptions() + $postScriptEnabled = $jobOptions.JobScriptCommand.PostScriptEnabled + $postScriptCmd = $jobOptions.JobScriptCommand.PostScriptCommandLine + + # Check if job is already configured with correct post-job script + if ($postScriptCmd.EndsWith('\Bootstrap.ps1') -or $postScriptCmd.EndsWith("\Bootstrap.ps1'")) { + Write-Output "`n$($jobName) is already configured for Discord notifications; Skipping." + Continue } - catch { - DeploymentError + elseif ($postScriptCmd.EndsWith('\DiscordNotificationBootstrap.ps1') -or $postScriptCmd.EndsWith("\DiscordNotificationBootstrap.ps1'")) { + Write-Output "`n$($jobName) is configured for an older version of Discord notifications; Updating..." + try { + # Sets post-job script to Enabled and sets the command line to full command including path. + $jobOptions.JobScriptCommand.PostScriptEnabled = $true + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "$($jobName) is now updated." + } + catch { + DeploymentError + } + Continue } - Continue - } - # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. - if ($postScriptEnabled) { - Write-Output "`n$($jobName) has an existing post-job script.`nScript: $postScriptCmd" - Write-Output "`nIf you wish to receive Discord notifications for this job, you must overwrite the existing post-job script." + # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. + if ($postScriptEnabled) { + Write-Output "`n$($jobName) has an existing post-job script.`nScript: $postScriptCmd" + Write-Output "`nIf you wish to receive Discord notifications for this job, you must overwrite the existing post-job script." - do { - $overWriteCurrentCmd = Read-Host -Prompt 'Do you wish to overwrite it? Y/N' + do { + $overWriteCurrentCmd = Read-Host -Prompt 'Do you wish to overwrite it? Y/N' + } + until ($overWriteCurrentCmd -in 'Y', 'N') + + switch ($overWriteCurrentCmd) { + + # Default action will be to skip the job. + default { Write-Output "`nSkipping job $($jobName)`n"} + Y { + try { + # Check to see if the script has even changed + if ($postScriptCmd -ne $newPostScriptCmd) { + + # Script is not the same. Update the script command line. + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "Updated post-job script for job $($jobName).`nOld: $postScriptCmd`nNew: $newPostScriptCmd" + Write-Output "$($jobName) is now configured for Discord notifications." + } + else { + # Script hasn't changed. Notify user of this and continue. + Write-Output "$($jobName) is already configured for Discord notifications; Skipping." + } + } + catch { + DeploymentError + } + } + } } - until ($overWriteCurrentCmd -eq 'Y' -or $overWriteCurrentCmd -eq 'N') - - switch ($overWriteCurrentCmd) { - - # Default action will be to skip the job. - default { Write-Output "`nSkipping job $($jobName)`n"} - Y { - try { - # Check to see if the script has even changed - if ($postScriptCmd -ne $newPostScriptCmd) { - - # Script is not the same. Update the script command line. + else { + do { + $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for $($jobName) ($($job.TypeToString))? Y/N" + } + until ($setNewPostScript -in 'Y', 'N') + + Switch ($setNewPostScript) { + # Default action will be to skip the job. + default { Write-Output "Skipping job $($jobName)"} + Y { + try { + # Sets post-job script to Enabled and sets the command line to full command including path. + $jobOptions.JobScriptCommand.PostScriptEnabled = $true $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - Write-Output "Updated post-job script for job $($jobName).`nOld: $postScriptCmd`nNew: $newPostScriptCmd" - Write-Output "$($jobName) is now configured for Discord notifications." + Write-Output "`n$($jobName) is now configured for Discord notifications." } - else { - # Script hasn't changed. Notify user of this and continue. - Write-Output "$($jobName) is already configured for Discord notifications; Skipping." + catch { + DeploymentError } } - catch { - DeploymentError - } } } } - else { - do { - $setNewPostScript = Read-Host -Prompt "`nDo you wish to receive Discord notifications for $($jobName) ($($job.TypeToString))? Y/N" +} + +elseif ($configChoice -in 'A', 'All') { + # Run foreach loop for all found backup jobs + foreach ($job in $backupJobs) { + # Set name string + $jobName = "`"$($job.Name)`"" + + # Get post-job script options for job + $jobOptions = $job.GetOptions() + $postScriptEnabled = $jobOptions.JobScriptCommand.PostScriptEnabled + $postScriptCmd = $jobOptions.JobScriptCommand.PostScriptCommandLine + + # Check if job is already configured with correct post-job script + if ($postScriptCmd.EndsWith('\Bootstrap.ps1') -or $postScriptCmd.EndsWith("\Bootstrap.ps1'")) { + Write-Output "`n$($jobName) is already configured for Discord notifications; Skipping." + Continue } - until ($setNewPostScript -eq 'Y' -or $setNewPostScript -eq 'N') - - Switch ($setNewPostScript) { - # Default action will be to skip the job. - default { Write-Output "Skipping job $($jobName)"} - Y { - try { - # Sets post-job script to Enabled and sets the command line to full command including path. - $jobOptions.JobScriptCommand.PostScriptEnabled = $true - $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd - Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null - - Write-Output "`n$($jobName) is now configured for Discord notifications." - } - catch { - DeploymentError + elseif ($postScriptCmd.EndsWith('\DiscordNotificationBootstrap.ps1') -or $postScriptCmd.EndsWith("\DiscordNotificationBootstrap.ps1'")) { + Write-Output "`n$($jobName) is configured for an older version of Discord notifications; Updating..." + try { + # Sets post-job script to Enabled and sets the command line to full command including path. + $jobOptions.JobScriptCommand.PostScriptEnabled = $true + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "$($jobName) is now updated." + } + catch { + DeploymentError + } + Continue + } + + # Different actions whether post-job script is already enabled. If yes we ask to modify it, if not we ask to enable & set it. + if ($postScriptEnabled) { + Write-Output "`n$($jobName) has an existing post-job script.`nScript: $postScriptCmd" + Write-Output "`nIf you wish to receive Discord notifications for this job, you must overwrite the existing post-job script." + + do { + $overWriteCurrentCmd = Read-Host -Prompt 'Do you wish to overwrite it? Y/N' + } + until ($overWriteCurrentCmd -in 'Y', 'N') + + switch ($overWriteCurrentCmd) { + + # Default action will be to skip the job. + default { Write-Output "`nSkipping job $($jobName)`n"} + Y { + try { + # Check to see if the script has even changed + if ($postScriptCmd -ne $newPostScriptCmd) { + + # Script is not the same. Update the script command line. + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "Updated post-job script for job $($jobName).`nOld: $postScriptCmd`nNew: $newPostScriptCmd" + Write-Output "$($jobName) is now configured for Discord notifications." + } + else { + # Script hasn't changed. Notify user of this and continue. + Write-Output "$($jobName) is already configured for Discord notifications; Skipping." + } + } + catch { + DeploymentError + } } } } + else { + try { + # Sets post-job script to Enabled and sets the command line to full command including path. + $jobOptions.JobScriptCommand.PostScriptEnabled = $true + $jobOptions.JobScriptCommand.PostScriptCommandLine = $newPostScriptCmd + Set-VBRJobOptions -Job $job -Options $jobOptions | Out-Null + + Write-Output "`n$($jobName) is now configured for Discord notifications." + } + catch { + DeploymentError + } + } } } From f7b50ae329f76994efe91b2a8fe191035a6a8adc Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:42:47 +0000 Subject: [PATCH 162/175] feat(deployconfig): add none option for deployment --- resources/DeployVeeamConfiguration.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/DeployVeeamConfiguration.ps1 b/resources/DeployVeeamConfiguration.ps1 index 3e8acc4..b635153 100644 --- a/resources/DeployVeeamConfiguration.ps1 +++ b/resources/DeployVeeamConfiguration.ps1 @@ -38,11 +38,11 @@ else { # Query configure all or selected jobs do { - $configChoice = Read-Host -Prompt 'Do you wish to configure all supported jobs or make a choice for each job? A(ll)/S(elected)' + $configChoice = Read-Host -Prompt 'Do you wish to configure all supported jobs, make a decision for each job, or configure none? A(ll)/D(ecide)/N(one)' } -until ($configChoice -in 'A', 'All', 'S', 'Selected') +until ($configChoice -in 'A', 'All', 'D', 'Decide', 'N', 'None') -If ($configChoice -in 'S', 'Selected') { +If ($configChoice -in 'D', 'Decide') { # Run foreach loop for all found backup jobs foreach ($job in $backupJobs) { # Set name string @@ -223,6 +223,10 @@ elseif ($configChoice -in 'A', 'All') { } } +elseif ($configChoice -in 'N', 'None') { + Write-Output 'Skipping Discord notifications configuration deployment for all jobs.' +} + Write-Output "`n`Finished. Exiting." Start-Sleep 10 exit From 2521bef84e1994b1c9337f0f2064b862231f6063 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:43:49 +0000 Subject: [PATCH 163/175] docs(readme): clarify installer script instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df66059..a8f6c6c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Send Veeam Backup & Replication session summary notifications to Discord, detail * Option 1 - Install script * Download [Installer.ps1](Installer.ps1). - * Open PowerShell (as Administrator) and run `C:\path\to\Installer.ps1`. + * Open PowerShell (as Administrator) on your Veeam server and run `C:\path\to\Installer.ps1`. * Option 2 - Manual install * Follow the [setup instructions](https://blog.tiga.tech/veeam-b-r-notifications-in-discord/). From 43f788848d31700efbea295e1f1abcb371706a11 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 15:51:37 +0000 Subject: [PATCH 164/175] fix(installer): bad webhook regex --- Installer.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Installer.ps1 b/Installer.ps1 index e3aa216..aa7de94 100644 --- a/Installer.ps1 +++ b/Installer.ps1 @@ -3,7 +3,7 @@ # Prepare variables $rootPath = 'C:\VeeamScripts' $project = 'VeeamDiscordNotifications' -$webhookRegex = 'https:\/\/(.*\.)?discord[app]?.com\/api\/webhooks\/([^\/]+)\/([^\/]+)' +$webhookRegex = 'https:\/\/(.*\.)?discord(app)?\.com\/api\/webhooks\/([^\/]+)\/([^\/]+)' # Get latest release from GitHub [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 From 1ff3b52569d5cc262811714ebd3be5db1cef8bc2 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:13:42 +0000 Subject: [PATCH 165/175] fix(installer): mention preference bad types --- Installer.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Installer.ps1 b/Installer.ps1 index aa7de94..4f062e0 100644 --- a/Installer.ps1 +++ b/Installer.ps1 @@ -89,22 +89,22 @@ $config.webhook = $webhookUrl Switch ($mentionPreference) { 1 { - $config.mention_on_fail = 'false' - $config.mention_on_warning = 'false' + $config.mention_on_fail = $false + $config.mention_on_warning = $false } 2 { - $config.mention_on_fail = 'false' - $config.mention_on_warning = 'true' + $config.mention_on_fail = $false + $config.mention_on_warning = $true $config.userId = $userId } 3 { - $config.mention_on_fail = 'true' - $config.mention_on_warning = 'false' + $config.mention_on_fail = $true + $config.mention_on_warning = $false $config.userId = $userId } 4 { - $config.mention_on_fail = 'true' - $config.mention_on_warning = 'true' + $config.mention_on_fail = $true + $config.mention_on_warning = $true $config.userId = $userId } } From 3c864dbba55452ca2ace0af7fcde9e85b6be114a Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:39:24 +0000 Subject: [PATCH 166/175] docs(readme): improve automated install instructions --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a8f6c6c..7d9a054 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,15 @@ Send Veeam Backup & Replication session summary notifications to Discord, detail ## Installing -* Option 1 - Install script - * Download [Installer.ps1](Installer.ps1). - * Open PowerShell (as Administrator) on your Veeam server and run `C:\path\to\Installer.ps1`. +* Option 1 - Install script. This option will also optionally configure your any supported jobs to send Discord notifications. + 1. Download [Installer.ps1](Installer.ps1). + 2. Open PowerShell (as Administrator) on your Veeam server. + 3. Run the following commands: + ```powershell + PS> Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted -Force + PS> Unblock-File C:\path\to\Installer.ps1 + PS> C:\path\to\Installer.ps1 + ``` * Option 2 - Manual install * Follow the [setup instructions](https://blog.tiga.tech/veeam-b-r-notifications-in-discord/). @@ -22,7 +28,7 @@ As much as I love this project, free time is hard to find and some work is neede * VM Backup * VM Replication -* Agents managed by backup server +* Agent jobs managed by backup server ### Agent job caveats From 7537232cb98ada44f6d178a57094f9a68eaa9dd1 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:29:32 +0000 Subject: [PATCH 167/175] fix(alertsender): footer message --- AlertSender.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 2be5595..b6fee8e 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -45,7 +45,7 @@ $updateStatus = Get-UpdateStatus # Define static output objects. ## Footer message. -Switch ($updateStatus) { +Switch ($updateStatus.Status) { Current { $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Up to date." } @@ -55,6 +55,9 @@ Switch ($updateStatus) { Ahead { $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion) - Pre-release." } + Default { + $footerMessage = "tigattack's VeeamDiscordNotifications $($updateStatus.CurrentVersion)." + } } ## Footer object. From 21ec16bb50001b90ec130de22999b736dcd7b9a6 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:40:33 +0000 Subject: [PATCH 168/175] fix(alertsender): bottleneck definition --- AlertSender.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index b6fee8e..ec8961f 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -129,6 +129,16 @@ if ($jobType -in 'Backup','Replica') { $speedRound = 'Unknown' } + # Define bottleneck + Switch ($session.Info.Progress.BottleneckInfo.Bottleneck) { + 'NotDefined' { + $bottleneck = 'Undefined' + } + Default { + $bottleneck = $_ + } + } + <# TODO: utilise this. # Get objects in session. $sessionObjects = $session.GetTaskSessions() @@ -180,7 +190,7 @@ if ($jobType -in 'Backup','Replica') { } [PSCustomObject]@{ name = 'Bottleneck' - value = [String]$session.Info.Progress.BottleneckInfo.Bottleneck + value = [String]$bottleneck inline = 'true' } ) From 4a912968600007c9779888b9047e0e130909f2ed Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:41:10 +0000 Subject: [PATCH 169/175] feat(alertsender): fail correctly on restmethod --- AlertSender.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index ec8961f..080e004 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -381,7 +381,7 @@ Switch ($mention) { # Send iiiit. Try { - Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' + Invoke-RestMethod -Uri $Config.webhook -Body ($payload | ConvertTo-Json -Depth 4) -Method Post -ContentType 'application/json' -ErrorAction Stop } Catch [System.Net.WebException] { Write-LogMessage -Tag 'ERROR' -Message 'Unable to send webhook. Check your webhook URL or network connection.' From 35aee80d8094f7a48aaa8f8c76f27bfbfed2b188 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:41:46 +0000 Subject: [PATCH 170/175] feat(byteunit): support <1KB figures --- resources/ConvertTo-ByteUnit.psm1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/ConvertTo-ByteUnit.psm1 b/resources/ConvertTo-ByteUnit.psm1 index 5081b1a..5d0ac49 100644 --- a/resources/ConvertTo-ByteUnit.psm1 +++ b/resources/ConvertTo-ByteUnit.psm1 @@ -43,6 +43,12 @@ function ConvertTo-ByteUnit { $Value += ' KB' break } + {$_ -lt 1KB} { + $Value = $Data + [String]$Value = [math]::Round($Value,2) + $Value += ' B' + break + } default { $Value = $Data break From eb3d52a7b064da12cde27a0eaf4378317c149997 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 22:42:22 +0000 Subject: [PATCH 171/175] feat(bootstrap): log session ID --- Bootstrap.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bootstrap.ps1 b/Bootstrap.ps1 index 22ca82e..b1f329c 100644 --- a/Bootstrap.ps1 +++ b/Bootstrap.ps1 @@ -65,7 +65,7 @@ If ($job.JobType -notin $supportedTypes) { $sessionInfo = Get-VBRSessionInfo -SessionID $sessionId -JobType $job.JobType $jobName = $sessionInfo.JobName -Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' ($jobId) - Session & job detection complete." +Write-LogMessage -Tag 'INFO' -Message "Bootstrap script for Veeam job '$jobName' (job $jobId session $sessionId) - Session & job detection complete." # Set log file name based on job ## Replace spaces if any in the job name From 5213103948f80b68efc5ef33bbc4baa796a45404 Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:01:37 +0000 Subject: [PATCH 172/175] style(alertsender): var case --- AlertSender.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AlertSender.ps1 b/AlertSender.ps1 index 080e004..c74ffaf 100644 --- a/AlertSender.ps1 +++ b/AlertSender.ps1 @@ -331,7 +331,7 @@ Switch ($status) { # Decide whether to mention user ## On fail Try { - If ($Config.mention_on_fail -and $Status -eq 'Failed') { + If ($Config.mention_on_fail -and $status -eq 'Failed') { $mention = $true } } @@ -341,7 +341,7 @@ Catch { ## On warning Try { - If ($Config.mention_on_warning -and $Status -eq 'Warning') { + If ($Config.mention_on_warning -and $status -eq 'Warning') { $mention = $true } } @@ -368,7 +368,7 @@ Switch ($mention) { ## Mention user on job failure if configured to do so. $true { $payload = [PSCustomObject]@{ - content = "<@!$($Config.userid)> Job $Status!" + content = "<@!$($Config.userid)> Job $status!" embeds = $embedArray } } From 80c230db12845dc3b70fa1c7cc56f2c1701b582d Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:02:11 +0000 Subject: [PATCH 173/175] docs(readme): update assets --- README.md | 4 +++- asset/embeds-small.png | Bin 0 -> 96438 bytes asset/embeds.png | Bin 0 -> 167747 bytes asset/installer.png | Bin 0 -> 199947 bytes 4 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 asset/embeds-small.png create mode 100644 asset/embeds.png create mode 100644 asset/installer.png diff --git a/README.md b/README.md index 7d9a054..9aa1b5b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Send Veeam Backup & Replication session summary notifications to Discord, detailing session result and statistics and optionally alerting you via mention when a job finishes in a warning or failed state. -![Chat Example](https://github.com/tigattack/VeeamDiscordNotifications/blob/master/asset/example.png) +Notification Example ## Installing @@ -15,6 +15,8 @@ Send Veeam Backup & Replication session summary notifications to Discord, detail PS> Unblock-File C:\path\to\Installer.ps1 PS> C:\path\to\Installer.ps1 ``` + Installer Example + * Option 2 - Manual install * Follow the [setup instructions](https://blog.tiga.tech/veeam-b-r-notifications-in-discord/). diff --git a/asset/embeds-small.png b/asset/embeds-small.png new file mode 100644 index 0000000000000000000000000000000000000000..be7bc37e20471b57db2f6f3d3f1877d5e0b192dd GIT binary patch literal 96438 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 wD-BLU*I7O|Mp)61(x8I`b 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 EpR|%}Pyhe` literal 0 HcmV?d00001 diff --git a/asset/installer.png b/asset/installer.png new file mode 100644 index 0000000000000000000000000000000000000000..418b27969407be8b8bc058764f23cafc25c80c20 GIT binary patch literal 199947 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

f0433kLClqkfVGzx2xf5*4=$0^ z{E5~V+%-2tl8(4XxI8>jmGjTXz zKp(m5?p@ZIRv`Li=or{IG+p2v-5NOE-5-9d9Zz@n5+2UwXj)okR9W9XBCgF~XJtCp z?xXi;MfbM%`t1TbEF|QT@+%_kzGoNnBAO=*ofwnJ-4KR<$WIxgJb`KbHMq@|w({N$ z@xK0Sv<5$KF+HVpRd&f4uoi#MHUnZ!zBBp)=Gs=fjVUnricpWfhqUoyjo;VNL~qLV z63>7R<8OetOa&VY>xfc2e>FTs+-n|U1-M&8dgmmel*M^X0*_Mnu}lV5KfyxYC4q?S zO+(KadjVoBr(+dpm*ZU@PvkF*?A3kI_y9h+>5%g4fWtRScykU39R82nPK zp+j}<34X`iR{k9KDoOo*zVe{~nT+i<@y^Lll9I?h4ai2Wh6O`;B?`d(*MQ{88{R+8Bu@wYazz^YR^=_oMoBXZ_aG`;bL z7w((&dc+TU9pj{UL~!;lqwg`!w{dHj4BQ0>Yj*2ICs9*;4=-*DYExX@-mM%D9xF8} zX(Bka+l+tXO{SN#_*;$xOkelG`6KhHF+&q1N72@*OQ+CZnzwkF-6X%2s!rlM2WQ7Vik<&5bBmjaP z?Mk`d-&ssGRmbPR1Ac+d+){d}ast{~>B)Cj=xCZ83)Qa7K8MXr=I><)xK2GAKalqa z{We@|!uB)(-3H?-4$Y>SG$_o;OlX(8%}_RA@BUMfs#F4|>0gYZ@$bB5HbWwYmp`nn z8ts?$Y6<+(%>EA@38VCQ2A|83S? zsb0&c#>=@x#gq4FU(JxwLC3aa60fi|4p=iz`wOn@;j;U(O;v ze>*x#-aE{$7^9Or_6H1-PJ+kt%st*iv9In$C=QKT3vy7&4!;xq#1z3euL=DXG)N}J zd!sh;`JOigh#P*)c6Nuo1CF^zk`{B6U$iO!$4CJ`p2|KrfWrDgGO!jr4mxVQC!7MT zkfss7#qb@4RY1=I_La|D>O5cvpWXU;cy@ke25L_CmNJ>1vL>64hF&aPgo?8ly+z1+$H)jiq|z4o>` z_2uG&L;ocN5fRwp|7HZO2CTR-8lsN{&13XMKn_H-y*-Qg?S%vB)ehEY0grGwmW71x z`drej=s4ywKqoL)kX838 zk?9;~AuHmjiF*3~L(>9u!b3~*0?hoI_d-aL|x)hN)a0K}{!$+s$@9}C)7+TWpHWggNewLi4z6^4AGaFt>${8lHQP#FF~!}& z!lsL9ObpNqpL40;u6L6?AI#cDuWoagqsJ{K_aV@;Xsm*)L>O`_qp2f6=1 zA8?|A?bi* zUpW|MZB}fO?&CHBhq;u{tJwbYxPdDuzx^xv?wxOD^coL%ATdX&|3&#J_Wcj#Ct0jk zYh|E9tr*9e$e_to)S%zH`}3aPB- zM|f0HdET}6hXlFqM-!JE2_FiFcot4;AB0c!e0!eA=mWCR5Hq-SkGWt@;Kd1=zJ6dqGtD2I8U*KgO6r-X9F= zc=~-S6;D@ZAcFqBY?v)8_>Npk0TL9cd0X%Q6G9w*fk2|*H5J#Ntcig1-cH;8%Gf`- zEQ2k2+n4QwQGDz!ZlOabS6FsV63)*5HLg|{gzhb1Pop%Dmb(?3=fCu?1Y2-}3;cki zE&C01c-+?G)o|oY_UR^(dsV?PP}o4N$d|n^WQW`KZf|ZDqh|=sxXZ#XZ?%Qu=-@*| zgW~ZU#51E3pR%qxudR1YZw7aCT<&?KLT3?bp`D-^DZIf1|7Q;Xv8aBl^XNF2dzD=; z8H9E{m^%ej3MBEwE;U#xc*&wh3tOXp<#Z@V+Xe_oS6X5VO>wl zjpW}NO;t#cm~37B+5Iw12SjlW`VrJ-qPcdJiqp)k`Tl>%Jnyp|?j$8^7T?%->m<_z zXjzp|{6z-J<_?!F3*=sEGkwx>ln@HdyJtI>JxYa|*r)XT$T4UVlSNeM6V!}!4iWA& z=d=ikm;Eo254pdoe{?zkr=6Da7+dkb%%-XzbIb1IAxBs-Ok(`nQ{w+?!s%e(Hps0^9&cqGHUPJKwL0qO< zv^pA1doJ9LMWL1!)i4odWuCAXQ6b{vf=DPN0nNTPntR^4`@q-@&w2>8cCdE>3UmcI z^MR7wU@G9`b8-BsQHjQzD~xrsQ;VA+DIC)SPzumL?D(Bzn<*@QSxg?pu0z8-eo4Nw zl0O>x>~>^>&5ygsNVV<9l}aCE?>`Z6#oVXs+RcYMk5yn@QsXRwI^g`=t2U*Q=D!S| zmfCe4Nah1k=V+qO3g5ZpxHQpZ*sPS|U~0wZE-Tf-teJ|fX(Cvw)HT08Pl8|ps+Aw%!xtH6 zSt{QWAfx6=pvs+nLv0_4j?=}CtDY^tmBibI11@{+38`WnadYS!cLEAisY}%mcj&bm z6*EqC2pawB_2K5o&b3S?HJt3tNgKAjzVvmAZ;y>PO$Z<5JGpKVVG{8APkFPJ|EDN0 zy92?En%928yq?_#;WkmZkQj&ua1*hJ=V!104}V_ApXkUpoCSVeIXDp4mV$55kn^a;e+f}5P(}B_^FO) z-??o3lhP~z)4OHueKZReb7KY=-;BDU9rd_pmT?Rk)D|vsZ6ogVtzJ2Jrh32E#t{0Ilf{lJhTfT?**#M6(AZ5vDPy57o2ABq~AM;atz;-eT#&@cDf!y#i301eaw)b0H zEh)b7)5wq>53lk5-oSL4oo&7HA-|2AT<9t=w5rvE@$MG?2;nO#|PDJ+VaL z()VTvFxFV=j)%V8-EuX2+p+8!m?reJ`S$)UgV7M9rTOz`M3p$ytaq6SvMZMf{7%l5 zpdQixB>ml*hH7Z1vg3HWda1AI%a&qn-yr&~Rj~XWFfAraj zkYtUti$(h(U0+ZBLw-WZJPM(5VFFv&cFemG4oqLf99<~;i}u5DrsRh;iq?A_zs4}6 z1b)Bh6{?7*g2-bhSoO%!9C`)p~G%`S(!9yp}c??Ph12BQ_D>wv|0lWggUXQ|yCXHW`1=?O1< zd#Nu>wd6l3(Yh`~eN-D91Uv_~Gcy~7rh+hUS7a_n6(knntWy2EF;9A^VK(lvy@XPs z4v4C|Hybx369_UmAy-s%-DsNs3+~|UZR16&m^O-^i%uor=o8XJP)gfGbE>1>wN|4g zIn46VDxG}O$K)qyoQ`yvgnH#!(I%!3XILh2O3xoapg#_=xSh~WVW$*M=M^)QXHxFa zF=ip)OS7DoWqarvU1t$Zgb(MAGoI<3?ze#oY~y-B;xfcC!$hK*mk-Q^_eO+s={y2> zxMj}zIX)HdLI$uwd{@GN8Z&=Y^G5YaX8T*}&QL4=PRyKwYCRQP9@<2Fq}N3pSba0* z(dZ|Q>3J!5hD2f9DagJ1k{laKq82x(oPOSRhGe^UHw^_`ghpW+kHImTbf|L0BAv8m z*@-xPJ<;U4BVvmIM#MM4V zuEKR!sRH=Lm&!zScVzXSwXyHF2p+8%-)?KPMI6V@fR*8Rb@@f5jlmnQAdY-b<|a{e zZNVf1u@YbHpd0sFeeBTs^wBY;;sfYc5DGluHN`W~k4JRH z=b8@y!=jpwC#_#fEl`xWLiaf2+i(Nv{KMpMYq58^BmRJEBF2c0Bd^5hxZSCDgU4lai-=RVtoWAdyHS7vmL?S-2cUmZI1PGd6w&QhlVc>~-&S{V5 zFLwZna4NkwyHSh#oNxR(QjRbp)>I2}N$_^y0@$w7HdOsVajc)8MJr&*){{^gYt3Iq zyjw32LA?iLbX!%reSXe1HxwiMOsx@*lT@sukuZ@v7yQDVR;nlmd4}m}gvNs$?YY@? z_a=LxQD{4h;)5xN{sf*zE?D6LyXAV{XFo`1h6Qf2bU$P!X0Vs<+oa(=r)_a<)#tNh zaxMh9c`{%@TW;UyX9UeIXESTRnx6t%=3UQ>ZRXRR0N{>o020l7v;xYv9caa5v~ zw|P$1h3yXf?wpCkTYWrh?cu1|} zqvP&0^5^*_4P%713HT79nYp2|PwivBat%xbrEAP1y}{!v$lw+_YI zTT`g4ZLXQfW3Bqzh{W~akDp37*j`SCK`99=PWHNcF}PXTD(+(fi2`*YNT2df-MzowuUYUewkjg*Xv^* zkcBX>km`rF3s2l*zCkb>iF3VIn!h9{wo!=Lrz?%#uT^trdn1gZI5MiaoNv*g#xicy zMYesv|0A1wY;AjqZB9p*oAQ^HbyGw~_GFtWlR^QwkFEYdJ7fRXiut0{qlis8|F5Vv zD`9UZh?&H~-F*%yuLK!)eq7rl?5h-%zO{Y6r-PKe#C#2khFTs)9{10ZhTel1JA?piQQ8l zKUSLwzH^VDf}O>1mDr14A&D7%@vrLu$NR-Hz?koj#TiBIdIKP%;~xt>@GOOH!Y8`LcONO#eDHL@bkW1%Cy3)+g>HwSBv>;2juO1@| zif)u+ru$e+_^^Z~+(3nEGXAX7xqv~Pg0{jz4R=<89Eldi8-=UbhJD^{Fu8wqt?agD zxczN?tpbF>(1Y^ySRwMh7k7>9{zYJ%3ENd-4n0-$q1D;?FL%oAtOKLe;NHcGacC)` z8Wvg|jq=uYt|R2b7O0dpNUkDr_VX`akeZYGc_1#L8ZM``^O=|`{otvX$CnuYEFZB! zoSzx{KCpS^;xn;`_=0$-70dAcLz^R^^;v5*>~#{bR_;2f@Zn&e7Zw$do*(O;b&z=U z?DC;tHNHr@x|28j*}f1#qvfdWRG6FjuzXr@e5i)rUGye*#>xbiHnnTyc|I}zF`d9K z9L{1M7Pwd`hp(szWVXT5S1G*-WQyNCwyc*6uSg9}qt znZWxU@k~eB_%x~j3+RT7BV^rLAW8OG$Gn{|)57NDg9LO4fWggNksRu$tQtv8FRoem=d+p@FN}{lC-1e?aaTHaEBZfrsZyiO z+zTexl{HIbU1=)4Z+c+=q4#?HXI-p(Jn`Oca;F!DWv7qSP1I7+-kknC=Y9g5(Ada> zl65JaDxB9u2_2?(urLE3Di8jYHe4w>7<1%UzmHT>9sz|XmyCI?Y0A3xqxblXi7cc&y0;~`&KON`}AozEbP7uRboOCu`*|!lsyZkDD+@pCRYjC6UIDBf- zk6JCz;70{UuE_EXEANcM55bhAuaNjF12dCeRE@l*rY4*IOoG9YIq%W$;~r?k1}>Px z64q&43`upZxnJUNMR30-k=gtfwEjD!i7FTk*#l79MVEMLpgZjpT)gikYAbYcQa(58 ziFkXcT{Vu6nbndy*$KSaq>k|xTD_3;6knHYzZQ;C_FTDVfZJ4%yH6m?`Ajwkemzq` zK??u@D@ayv7x!a~F+-BNjt&Psz9i$z4*W$?{|lG(C!#PHDL5rDcf1ZXEOCD!V7JTb zKATVJK@wzN+tuUquHV;Rj%+1DNi*LN&*z8MK-6}?cE%p`jWFU<5!I!(;q;h-T%~#t z{BdlA80x3wP$++N%1aF+VR3%nFAcZPn-}t;cVE<}Y_|Qcd9S{eLUel5(q^jDbAKP~=ZO8sD!%R90e|P}d z{p9lxcjF@;0mq>S-B?GCtcE|2g5t%oaBY9vWEM@R(QYjnk=*B%%JFGGJq2s14i&dw+;-{}U|x@XrK# z&9bd|=swP>GJ@-Q3cP^!utP*is1(I|KsCVowai23EKBroIgHAcKEU*v#Y+xzX;E_! zdcWlbo%7kSUU9mAVdS?O)AitSA0&NKX!bO0v@`5%E*i6}HvyiH?oPSsPHFxJ4o(Cm zCP=M^lI(IEnd6DIDAutB>b_)xTFgA04o*j7$57OY*URF zn3yGq`EZ%<%h&$4?4En7>5~4_O?ITda<)O!r|kKIGJ#ATCJ7k*wJE8*1^0qoe6cm; z)ApO4yY9b{9RAu60}Kfls1J?BVe=p0!jFOPq=h-HQx-fczab38JUUy#B&soG>U!`o z-Ed1#X@@?S(@SToSODP8%d)W5cQ2tkjFbFr`YKC%@x}4k_xSp*OQEh;-`Tl8!)Xx6 z@X3l_%7i^LX8!}lCxXJsf}Dv8Ob-BM@$Y!_Z-tUH+S&*UenP0Es^2{3oG&rr0(_X# z!SEiHyTp1mKi8l`c3g)V=toDT-$bb`YLvD5_@U_TokYBW6JMNs4c3R4d~&;cE#S3N zH|19WLvT%#1)gY;3UQ-riZh5ZD zTenOTvUTPE`(!_`QpkeM4?!70PJ-To$dbsG(jyy)pGDUc3TdgQ#Sh)2F=Qj$ zmPs@UTTR3$R-)l}@eub)?Bo9dqW%2ok(l%?; zq1P&C1ywqD_Nek>e1Vk%Naw>JG$WW#{r)ogzl}QB74X;><(d*zR)*yiC(%MB*i8CL zFz7=?4xhlbbo!mj*UnoC>vlWzUHA4nF`^inl~o=3{XMKtQkQn_$=X`G=4&A$VyE>l zc(PB|s|93OQkXNAe~SS8{aFsN(zc>!pHcX2hv!oB=ODX00RC?U3B~;FCWHi^+B#OW z#}dk=dAfdCwSlliB3IA$0OCWIMMX zyxuEVpZ#|~*N)&oF|cuwa5IujYW=Z~?Mq1syQW(2!WqpYZiCg-lfwl9ZpnqX><3g( zM6ld}DDx{pczev;oF$L-rm)DHKTAKHe9d$^jo&H-VNk9O|ECTsWTF&4tt)>K^r0Ld z#av$0{%$ap#MywVn;Z!kJak;s6cp>f-z0sffbK@2`T9y^OpUB+J?IK#-T4ElH(Zpn z($o~NJ>PvJ%A~6nB1%*r2FhRiOSAPa(EWcF%=iu6m=FnQ<1BR|r~IBrjZ8QjA3oP$ z(;NR*m4&7jm9pQV-__@5Pk35z=LrP4O{ z^_#~18IAGfQyclZo;7Fay6dXjm=5vWoE4@l$Ew?VXKY%dx-W6aWM@448T(X~Jd9Pa z6N^a2e4|Vx67?L6O$(cO-aO^tSW&{XEm12VWV2 z(H~gR0Rm@zStV_}@1<(2MGbs5wa)9ZNzSFc9^}}Um04OH{CLl!+3be?#Ql7tesb+@ z+E)6gWF|u(;6aq=g80bR0&)C+;#|a8kevEMruo;j0-OXswVqb9H95T99NOb=%3HR- z^9Pi(5wh>)x_mElHf!3h@i)W$?I#_=9yn@2S5S)^Nq+k0F!nB+O&US{CSNtSnd1xO zQd((415YPE9OE94x@ote-y}}U^M@}Y1!f&sVanB=5SHnw37A$$hI5suwm@I`9@kz? zq`CY79;CG1!czMA&h5S6H=UQWG=38$6cx55W)?_oLJ!BkNDu90+YeP8JqTIY?Xr!) zC!6^HBfs02B}RXJ7JaLBD>j^d?Ivb&hbFeanUOgCqzhx%h>cF-!@s8h$B*d8^p$c- zt5ZK4Z;qx(Mql188Jvd$;cUFHNv0d0JAntOE@6(gnEPy<$HvN_<=Jfy_<5&U!(?y# zEuW-5K?U=i*=$2)URec`vk;y)UhV|NaX z?|A={;Cb(srZOKa@eDe&tF!aH;_lrgRlmih%}8s7M(Rs%_2Z|V?;!+ZYl4?T`Mc+j}<)(m;oQz!`A*S zSiE;f6e>*fhCPiopzOa2nZLFYlHJa3UQ*oe{W~?lUK~wd9c6z%{^~-hCUiBrwtec4+ zy7M#VGr`-dT+KO1-+V1pzAx_^AgQ!Ja+cjb9GQwukLBmqS#jKh3-KYzhT%TMHa6DU4L8liK-FTY<@wGm zsgYu8*7S20gW29al&|i0uSqA75O2aZ_^;JyEzAM9`nr{ME`dypN zPSN-L_RB}@w|B2S38C8p84`ey`k?JIHmz4|kk=~V5shM^9kDVdi^-ogjf69+aP9kf) z*+qh-JgodCy^f*p!wHMLBlM@8qCvSm{FLMN+BM(kTprcFpODXL)AeZgLte$f>QkzI zgq%a88g*)mLAKtA2%>=CZPg9jKAO5QQZvMYGA;xfST)K4as*1BOT9ry>H@MOe|j5O zXk)>*olKXB`i<^!;?oM&l4Hq7hSef;{i`>2+cOpa!?(HuB(C<)YE8N#T;Jim_#-K- zX-9DTIiaw;_JP22zS=q~u#odp!~$3y)6(y?qS5G44iiP<)z_5M{2+{ny2+*wV;xkL1%`!JyD~ zl%mJx=m2+;J)u46L?x{TY%il~WBOJF1I$KYS!FA-c0J=;WcjH0LMH9C<>;}`Li8ZR zi?kXhw7vp|+QzALt=*YhsUIkm3|rGCNRgNWy+l_WWvwpAhu9))vQaA-x3YQ?ACa&U z{&uyC%g~Y+RlJHNN7kZi){-vd>2}#A0VgL-TM5CO7!IFRc??dHHhQw?El06xbK38t~jxrTlVfP{FBHuGX*r zfG~HEvR95?tA6Ha%6N$e7{uYhYm-2wCBJa`IQ_YEKQy6yzjln$=FOcNjZ7 zPzL0+aV)L;s9 zO0KG|pd3t>>?JadoF^l68e}H~vlsE%pw9I`9Fh(LJ5-DtxL|b0GK}f6c`q(rGBS&4 z<%m^9M<=z`%d`LU8E9QOQTVa3CGzqoLQASJSNks^R8Lbm3^?tM8lOd!BqGy^JW8^@Wy2jIP+KIrT!0CIx*xuusOj-Scr>b&JsMwjx>4g()|oXg_gE;lz$zI zJlD=I7ooM~AvT;WpF-$D@2QxMmPq6wW$T6nFr8$j!h7Yt*lCjl zZb`NpmbhdeeV}1+i6hc;)Wu49WIujd>P(7HTA;cEn2PdT|3CJ{!%kV`52iW02snTH z)9Ws`)OLkA6ocR}8P0jxg9IkYbAm;F4mG2tq9mWYDT@2?WVF?%Ss-dHj%8Ix!T5hc;!L<+}9KghEi!1@n_;wZMY> zoxCUP#x&9ZmJvORL3FYASugfsR^*ElpC8bToS(TA$XpKjcD!q!(@cI(KGUe4Cvui* zmkA=r=lpS>M<#CiL$)9eLk#e>@;K!0yX~2=I3tq7VwZ9d9F->veAG3X@=^)mbaPmS z7-yf%XC0yi45B=p+z%yl(jLq0z#QEBIo(*ByC{s?Z4QUJ%vTytX}Ki4bKV4k%)Uwp z9VGIZntyv+>mIF|E|I5K1rgaN9h-lhl-v*?b7^y9mwxqZyj)zYjji|GEfOOW3HUUd zqpNQJ1A{&!La=LdfN8rw@8+KQT-+&}(V^=NB&3jXXgnyEcnu(3sfXV`fs(oSIlSA) z80%W*?+)vah2ce}yi;Gq05DcQ5dA`h}>E|D8r6+KjWV_mt#~aB{(SV+Yv2V(W z0%?H!(laEgQOZi471q;bJUzy?~inpg4zOT0ZtunNZjXuFYo2Ny$@gg*w_Zw z4qi}Zf9!+?^XX2O&3?GIM7~9jX%=PiqqaL zu2*q)7!bC8JzwL|y@(ec;7$FNIP&tUS=0B~2>FfXBTN&#Vx;>G32}Sh!-hw|SDXt0 z?#oupuTt|-sBWp}vN5BAHJuI_gV2RKbB5rNk==v1Bhk&PWth*OWYQh+TZONzG~PI> zkxn2zKfAmxeP{p1m;m)3Q4$HO`6b$0Qon<-L0vjc2qPdOU09~>c;S*u)Nhz(0`Xf5 zU_i#TGHw%8_AVp*FvSdVIjz%Yl%fQuD`J>$+@TS5o4EMYwt%epW+viduTa4XJ-3{t zN+x%A|HYavImnlEp65z0%c9cjX|$6rAU)oAeMQ6X=A9%d?PgVENc8|9C2j^0B9^GDaB)pzdQ8=RP}H^x3Gq04Hh;ciIxn1>)^=)c&=F&8~jK(yZ7YjqUj z7BL5)Y{O%MWVYh?kKSFv{M2aHUeB-O`05Vmh_8e`W%qI+I(S!Qo5^$32+C57hMQ%} zMJiaWevK8Lp`5zz3i3muwp&?NuE%loXAJRbGM1QOl+0h=9Ty^b1RtgM>Vm+1D&;iTBx@5DLn)#XCHH3l{Q#gTd! zV||Z1u=!a|!h)Slsgo%)ML9g43+aSh2S9UqC#(c{b2 zWQ^9UU~%jO;5<%Yvg>qamFr6Bt;HROnvSpGVOoRkrKp3BTE&+s8$L7g>+|RDv$?1Z zCLytxd+wq=OZx9m?H(D8(kwIlON0eU24|z*NG%3Nb8ZRyc`n*{wG?k$6?J-azf~H| zmsJN%NR+fabUKy1L(E_ z5Xd<*@xvr1wsHZ$P%gl+8o?e(Rx2i^43*O)qA{#Jla!=brqo3=_DpO|0$QAs3pb1( z)|HO7&3yD-QBUv0M%-KdUtEY2X|Rrwk6OL`{KVio?XsUEoAX>6e<+HM9(AT*e%##gwXr@0!n>;4ZWH&m#=i5{^FMOckwmaNXRt_2Dj>;?gAPA?qI}e% zFOvo%^JTMecKM!VlC%cX+t(+MDQ*T=uo82}OST080d`>ta zGCM?aBs2Qrqqq<($D3XiD%Z`jKwg0}g#9c!>+zt^6dmEt9znpS5rc;`&~ok@3ElW` z-YS0Bi`GEYNsmsGHH(HcGrDpgCWe7%_e)vEepQq*+*^9uxjp=~lFEKU7`8koYc)B> zf^8j92Nk*6Iwuk6pXuDQRce-1{f3#CPg&TsV*$33k3L;be{hK@F{_ngw|_bR_Q`d> z`yK7q(2YDj@`okPdZeuF1z>y*Sak<5ApXc`A^cRoT*gY>7vIw=Cx~!NVQDRvhlt7h zx1r?x48l!A3wEb-fJjKIQDI@_E1nFOnQ{D^_o_k5bqt;0eDkQg^6}!fW7iHX#)bol^_L#2mt0PNA0H z0Q2;E2>B{2DU}ckuhd6T+6?Ar+{9By+Z=tbM1k=yJ9v%;fM)QfUV)S}DGQB`kBaVk z$wYHDRg){<6{R;Qe#A^qdhU7q#^Cg;kF%NR<8kRYfIcAAlfZ)-@{j8 z*o`qLmrOo#K6#m%AR)OhsFMDj5}RtaG&lBgKf5m#Ap(s#T;fXvbOBDg1U7J89}JX=UGKI5TW}?lgHgU~^~BU*uixJd ztg?m>0Q>sOkmHPibZ6Z6SJyLNg(0Sw=W^?fO59&R7Jp=oxz;(H5;@-t zfa$r;;6fAEN4nj|GJv>nqHV4Zrd8Gm;62Q0cw8$!(&FpY2Q6^liwgLTZ^+m(xKprt zw|vLdS@#n)4`0UkH&BhJ>#tS}`S3ncdl`g) zD@wEAt?dxx|KaN`!{S=HZfziF@DN-YOGwZl!QCYU2*EwLyStO%9vp%OcXtTx?(Wb? z<22oz-ur!@bDq7E@B7KMxaeMsT2*t7agVB3{~jYJL5M^2%aTwPO3^T&pjGio1+~ZM zw>7j6c0GN;%p0PyZ({)=8)7m4q4hY#;IA}JHtF5mel91y%)Zue_Qsmk)I(uYuS-6$0aayRc1#PtNl_y3) zp;%rL=wV6R+tMH?SH7%&8DHL7vm0-}6r_Ri3wB@wq$a}JJoYrboj{2=g4;F4iGS%; zwE4==J>5_2&J6t5d9z<91wK zX4W)&JhYhBpDMmyue>u{S)4Vv4=0}=T+q?X%kxfC@s0n%WO2tMx4T{rNoP~03u>>P zGBHp1lEPscx#E7ho>0SOA@m}G+x9DC>xLOH6u=)e!Q~<}4*7=D4F|AUy0J|4tt*56+7q`f3i*G8)=J zJN!bDY1rX6R{o>36^LG~FswJ+D)r4J@)hR)e_zr0&%Ofp{M~f7xJz{{+&vSO zq>6<2UXa;m--AX;pp*{YetF{0U{Y%|q&u(E)b}QV%gQPm(c#>#K_lAj!cmO6M%G;I z!?%eH1X*-qvobL%zR?JlHdg8#x|&%0(pYZ~O5OSR@U1>Y|;tSKhI1r5iPF249UbKqZMXQSCoFIL-&RDvqexo z40DmBi!)`#*dC&KnifL!=3o7alCsXbjm6iC?PB_AL9!!c_jFq_acPVu;+Ezp3OjVe z$;Zh1uXcu}$`%q*m}fU3OuT|lhkS$H`69${{Gt3&@ZswS5#Q7eHqmYIzGRZ35>eBDy!+V-4WkMJ>!}C-tRYw1dj(6^_-JN8QuWs!ot4v?BS0P4Y?O+f*q8YSjyRsA1R{k(nQy=B3%p@6U?c ze}_%0@hzZuz1SObWY7BFi|@iFYTo=LzDiRo<`?Jl2H` z0>yj%f3KR>uNJ()GI#ikhV}7s+v(&nW)O z>Tk*t93{)H!ix7x>9QEXaiVU&NnYfc!oU-F4x-yiQJB(5w8|;^0B8(nJiRN0_g@Ik zY_EaPy1R|74;f>(PP}}%Mq&p(V%TAcmw#O0!5N&BzdaZ@yQV)WCWosFUUa&ed2HNl z;0sjEdR4JK%}=&m>a2$8@*VvQkLOP{HEP40_;-bMn90Mw6DuNcHcYG7gERF?NrcDu ztaX6h2!VUeG?L6;F4JNqyJ{$2s^#2gwt4!tC^$UVsVNS*Rwc2nZ_A}yYeAj={D-9Fx#wJvRM=}o^@UalIBM#AJ)7^8KA+n0@lJ93HHbw`u79ti`f-gQR@r1c zrYDm@7R;h|%>JZ(RXZlTpcj?KrrXRV$Y41y_^LmXQ}<@ERo9f|AR;#6#*j0V#&ylb z+B^1qd_#FvQ#bbCLi(5-+?c;6ib%FnrVNfv5Dn)pI!zs`%WXyK82Kj^K+hY#xyya$ zomD;8P+V6c#f^P9>d*sjoaZnjnt~E))iLUsuRbh@;#!kt(=>0eRx<$ z$qBqVyv+r}q`>(>m}X*4v*{LWn0rT}?4fNk3s@qLdPBJi)rQJ6T^vbN@6;dXb1Usj zTR{m^IAIImJK~DU39LzDlu=Qt+q@BhYZ8r0kM=z&k4X*6+rbn(X9DSlu^-a)Sg^IC zrW!XnIx9Ak^d?pz`aVV5e(@=G-Ad~f%XqUa*5Q@LYb5hA05;B1Ik$PO%3H&3nwVyl zUv$HQeewT#fN*HShI9tE0#lkOabAZ3NLy!r1d<5^kJe#*Dun%w{v}B2|Y53 zZsxYGQmxdtW4_qY3sNqyu@G!7Qt@$`F#!Pi&vZn%MVi>r5DsINH( z5Ts{^Bh1mFK6{nebbrmh&5Zctn*`pe9h~^81qx!@@{40l_Ys>nhZxVoRMK{Fs05Re zxFb%vnA>4x1|1w3lEHD%w@S5Gy(3jRe}!_m?A8Ja&!Jth^kRAS9~-+r&1Rh{7e!UQ z3@=*+ejZXn2Z53&kQd*r$Q%9sM0eFbJ)J7K zQ^~Eb^pF6eIHe+d;3C_a6&yd9d+OxL%Z*5lA1DZ_;BuxV6|KVQ;zV`ie!1eWI3&iw ziH*~t>k^IzYTT(0(SE}+wTK=|TSHjO_F+bF#*qtz@MHH2`_J)bXp;Bf1W7V>(V9Cmq|$AR2n$7rZ7qBzX!vmd^C`_b2Z+T?q zcwOnKhv1s~AM{T^OJN6Qs(Yi{7~CKwk>%;pph;nvgi~@@cD$-gA+ci!in+?yy_s!S zq>lb=ZfD0sm2JQZlubaJ;C(yQKzYEb8SPhhzDo=j6T&-`iT{_=#hgLMO^%bSoP&B1 z{v@J((CCU>neH1=QI*heAaa-5AX$HVt zvgY86*T40ueuy9DIVTQ{B^H%miXPA%HNqz>1&Dcsbyg!b50(9lCH~-lL@B~Ohn{pFwX@FORU=XF6d7lqjD~Oc4OL)%P%no!cH8D5$7=MX>}@66WX!|SO+Nj zvTIjM_AAxb;TkW8_-a18v6Uu6X&a1YBl?4Ql}+ql4Z~YB3i&~iUv~xI0|qiUOcP6W zS#^-J;5!DncC~6lA zb>VIz+9sY!>y3iOe5{?kR!VTpiGQ{``VpC54`r6yvq~8Q+aoER?;5b9YM&H6JRofn z2cb;;!dGdi>d0`qv1H82%)a8h;amd?I@Rk(=dX+PKmEJhrSyj#VJWe#NQlD&|F z+L{%mmRT*~;jg=yvxu_|F9)mPER#n`6z9bbWl{v}OCy91HQiU%oU-TMhL=7lwev~X z6*v>8SsNGnum7Ucg*S7=yy9eCx^--}{-Tb9AtzZ&y= zb;hNqs4+`pZC`u{{lxYZ74NEV-ETe4Dc1CA9T-;J%>8=Y5yG$DBMvqFu-7k-E z&X71fI*OCn_f@B<(KGn292>#5?z^IWc%WW(T`~O_Jrs8-Sgg5xizUp|G}Z2KxC&H- zE^Vzkz~XolErJlCF-&$aUkmSND@#(|^6AMtf@Lw`&vw!O>F)9QjCUX7XQ)0ct_qd- z%&h*P6clnztP)wCTN4mN?s7N4Le2AgB?U`a&o7)rbpA3-^_64_9ZpboP1M*{hy!OX z9vQ#(e9>5w@9xw02f{&^2BFJ5hGZxz?{e>KpyK8!BAs>9+o3avWuOoRTDSr&dpc(W zg$|b*Hw;ydh3D1PsYpf;Q4umNs+DVd`Saj^u1mFJdi8?{U!Pt`vT4|sHw@ijVUTNZ zWl|`1Fn4KZ+mtz>O;AYrBLJCdonOBh0v2+QT=BlgbpS8+mtgkHhC~mUbMc$hJnPnf zU`<|Sd`93sX4UF8NXq6uWUp$M&L?WZtVbzDz!DcrGXsq+bC?8q?|%EWKR98eDz-lw zpJ(k>qFw0}sGpC($%T=IZ!#M#E68<=o1)q{c|;-T)wuF_W$yrP#T#v(;BED%1afpj zmISaxm#$a=0?c|s*3G3p&(Ntj7POwN_j*J+8SvBnOS^#C)#hk37E^Etx5F@R=yFo5 z+U(xMoX@&Io7XsjpUt(rbg_AM>dbjZo!EZkDan@Q?x&8}o$IPY=;MQ*Dy)H3YulH@ z25Fj|3kTuaXwwM1-t2&$Jcl`6&Y;?;m5!@BWsvr)hc83(snknnx#ffCA@rJo1VC&ahblg`VGfj6+a8vB+pWAtt$2r_75>;DFO|fCQ-#q`=!~fO55e zWB=>MtmrXHbaduw5f(!Fe!imS+x-Yl4I%eYoe|i<$VDAi2Qx}O&fGz2j3Ip~MG7YD zcLEKh0VF;_@XtcOL&jY^SKkN%Y(Jc+(RGbKnZ2J|Hp9WyxIXhq~;Uhf1*`kA3gIMuPa$XJnc!g-R@@61>3%G zB$FysKrP`$ue~65sUr!K)RE1>DQy>ExAXaaZr2dF^2>!yUeI2$QwHZWx3n}SxzNaY z-;r%+N7*>SCMgWg01)K0WT2>}YI|~ z1;aa}bBC`3b>%hL((pu#-F27ajC~V?KA14JzCxCZ4ytU-EwR&{@+71l_Ypp)D zmiHqy#sgDG4=#ZfW**s*J1X9R-Th3yTkj@331;-qPp$nNH!dA1V$u6fKDqE~*EEaq zJcVito{DPJy$56Bwv~k+{$6ajh$7GfhqrfI&YH9&s*ef++{*M{3HyiQIN16?;Zc*ML8wq9x0POsSI%YU7^UG6FnTi=Qfm+2#$L!aB~=chd=t-9Uys>{LT*5@ zrL4q6F8ffjD(lbmJt|9$HR=I<;ZWh(S|i+J3-KqZTrxlJh9cyXGO5P>ZSe zKCb|w_E%M+dUzJOU(J>k!O^!@TyW~i*ukq_Df4ZRRXfi+>j z2E>i`elf>57j2ktga{jo6eSl=z5MjCY$?N>D-gLcWW&^y`LSS$LnpMdb#l1MqIQT0 z3K{0>^Qxbe;*IrM)*;h0B|f#pV{xK0)XQ-$p375=kdGdNe(QtKAcz^dX!uz7yM4Iw zVL5{8Vg2X76;7}&O|rNS2$+R8Y}Dd!u%zI{-;p-}qs`xl5M zQ3YbzhE4!>WT&CFuUd}J54z+NY+_PU)ZUvRD&8W$`XHR|GRI2;Yo^r;TV2lw|?fpUG%oy+#O~o)>S=;+CCb2 z_gpdgD7TS!oxUSP!h(c-gL5dHpY(KWJ{jzFnUXg)8CR7_1Yh>r>TflD`aX}|55JYR zloc_R%g@4|ePO5yDZ-0FcxC!QQLVveg95?sp^5zhLL^F_l&1#JkFM@s#wtDOz0t+* z6#v85n(z~TmZ;f=(U}fE_{ROa0Zy-N^U60I7ZEZ+@YW zsy+iS<$M4^;LfbWPK^V&zO*6&) zp5!zL|3^|q2XX6kW*IXTUlX@6@v}(jLzvq*xKVj9nV`outI?VC9jIbRTk5>zIEbi0 z>-{Z6R{$)g)ymB>ewvS7{4xa44x{{&0CMR0nNr+=RC8&u|p=wbr9b~4g7UjMtw6(jr&E(7r`5&x^- ziZEFJk^;NN=Xh2F{QV~R$o;vaJZp(>W%J`EWG0?r+Wv{2jFO{F9=?W2{`k+4c+os! zHZlKnV={j|k&jCkR-Au$A~P?fdC!dJHjWZN?$UUYCV=Auj5& zn%;OmDZU-6KICEAo*Uab+CfQ$6gPP+@FAAd8f7X^tftXPqpP5}m3Ul%*E;u-+l5q& z|3pGZDISgEmB{|BRZFtcD0ec$0r1E(r#usQ>EzEcHM{@p*ukPusNW_!d5IJ(29Zvy zhrgYkZ#(QzR2?^5=tB93oRrtqX(HDW3_dQUL#%j8eoBnXw;VY(c~V8DS^bUVo>+Wx?dwVj<>k!g zXD5GkR=q#c>V#%+t&SJlPdGiEnybau^CS|9vmucnDn;BZ8=5W0!OM;N&u=s}{my;H zsyogeM~0dK{@B!A0D~vA`2kqpHr(ajfPK^LV(qR`+D#RM^>B6kYDw1{VE*6K?X@W* zf396BZB)F+xD@kSY|bDYM8}O#@E!E0vxhj%j6saYn1^9UVvhTfH*D(!YO@Rss4Qm5lr{nC7@`a-uKoeqIGTw{i=}q~W;(n9I*L_DT`>wXl6h*VqaZ0w{FASC zU#^?Ot|xVe*RFD@*~n$C&1%ABNmob3c5|@E0V)L7k}{-qfWwym575BMat>O0lTtAA zDfK$Kft8J_I7u3QR2s#al&D$w7jBxWn`alrKHa#^D_5WUp4Tzc8un`=K09X&9ZL^W z^KaI(q`W`sJD@)B=Ld#=0CGJ;mXf(=h_O(f>PUSJ%s%>mn1Xq%APxK&Nl@uczV){Y zjGtATi!xea4mhn$snqtO3-S;%n~wdm+sB-hQYgAJhhTI5b-OY?-?6W+qRHIl{K>rt zw6pmUB6mH$JS{lW{ZHhUkIO%4;*opZT6F&qpKxf0^hxK(6cyWX`Lc0WYox=-D_6PC z6S1GjWte{!J*TNc5(xdw_OtX$Ur-ztOTD=J;hfM0xiCf~{{{jaJa?HC_ zv)6h=Xdx3;iar>VD4Y2RjLbiAn2U^ygw~2*y(|tkG;=yK`|)8pSy{<^iyPOs5wPTs z``I!ws=7awIizz2_sh=Y^R)U3L~w2CKbT|723|v^5+!g~xdyoUq41MN_)?R6ID$qH zWt=mPum1x^LXlCe3o?TlS7`?JzopS+`p9 z+i`QAa^Z!m^G|Y}=s_W`2NpI@LgyU$Ga;Muu>$on<)L|VsFd-bJ3z%agZEE_+?&bcp-_v`OYd{X}|Pc(th-s4i=Ab zI|!Ii{w0+EN+Ihyofu+@P>=l>;2jyYm!}x<{cvI}-@6dl0s}LQBi!F$HA^Hu?!Tvy zL!P*)a~+QnNkwtbbt@W~*asgeD8y*-&S|D@HTx#AM|(tna{)}+4qjIjuW_{Tep(U9 zrf!e{Ba_)jt*rZ>^^|J@5UpuF0Y3FB^Hri_+!kt!w%u0l2Y~V9aJ5p_<@TEtg<)$T z`4Y;l*<+@vEjzd-vttBWVxiH7?hVz%n%~!3++0*3(uKTzvN87dRs1r&&$4|#c$C%N zP@$XV9{rES-9-?=m`>y)QZ)}MrKAdX;*O!iLmu`(L6nR83$bGQJjw#uuSKGoAM`>d zehvJH?)?^##i~M)b|XW~`eM4eVIW};FTz5VKS=ezL>)Bq55rhA9UAvllWT7sZ4wo% zpat@1fHjX0FIz*IOm!cB?TbJ{Hcx_XASA0*^0fUCwFz>vT^F(0Otz8hkjji?=kNI+ zB7>Gjs@4OXbjF~@D5#L}hM8JPCilirfG^4ZAEe0Y&tL#8*7Y+|-^}{&*z#Y+je#y>){QPpw`V#L@(s0n3>fSBg_WiJj zPxZq-&`eMAl34zqB?6Lvb-b|3Hz1PTK%S*@f_n!4+yyoW+(ef&Ka8T3pvGzupnS1` z?HObjc^SI@QDyQ{Dt+hyQ$gl0J&VY)GJb-Q0t^0wPCp1St-Cxoye>z{=j5l|#74SP znRx@FySwO|3qKuz+fH9&U^XeSJ<3VE(fUeiH)Ypxhfl~;!=ZfXi z91wH|Z$0Ib7tXz6X#|+7i6-Ou6eV!n0s#K%32j9EQ@^T zU&b3g9xsn!r_-TRU|3G3@RiL}c2&grUTOaq^wN(pSJzPC{h1GTd~ZHLKeySYHrnZJ zz-pfl=32+-$r*!Cr8R+Uu%WTla(K1TW}pc+nfg2I8nJW0MSpD@Z8#xh;9FN43IR*J znpXcnqYq@LhF5j$K0^DxGd!uFQ9p;@9wwhaCB(6YKg{IgG;Zpuf?-5ZwV9FE-aTPg6*9v;egiAd%|EPLTN)hQjU%c^; z<9(i~3B`BB(Eof>%4!`4-h`0k26WCD9K+KDIE(#S*GG81HHG+k1sx|=<3iWQgQn@o zB6WZKr?UR`gL*;nD5z6lDJh4|V+LB?biDYQQ_Z4N`aD|%Vk}lT_C*oJzfFL07l-(r zUD(NWS|rGdt2cHz4wh+nkO$vMpN*$2qvTkhybu8*;JI7$CZSDy)7fNf@z(vg4ZXNK zSd2WPJm-8V%dnfS#1Kyp3TR+v)UXR_9VesB^f}&ZDY5DeV}4mTO2fJim;N@B0O|cv zM?)ZB$oA!ppj8Z6jOWKWp(Y71_VL5!2%NKyRrdJFS}1m$w=p2a%4Z?6r&W0x>sjX!96r$2KFM(f z1<#{f1-+&D8mzT{kaYeT(ZY)|ipy63MqT>C6+xYH=;u&vC}f|hITr30?{>Y@l$`OJ<*+XihVp3rFUphG?+_OwcttNOna+n=ed6nRpL3d- zQx6FYAEoWD$qby?$A(zNZ{OF|ir$X`zi?O|;kN$bws0zQV(Fa!{*v&H(-SK8 zQ9)~3=&M)-7Z4qCI&Sl(ZtA4HPwn5%Wh)r<>JgpCya(XhB%D<0F1GYhV0Mi?D_L(i zB$9`ir+Kt@*N>)fsDT-fhB`X*BFF>vJk+zyPp+`WO_?LH4ss{98W=uN`Y5>iXzH-| zeUdX0a6ERQiX0@_JvMwh-pc=~gfAqEk;(Hr_1<iJYi^ymTweQgGLuEX zL)UN-R9vh2p@LTmQnFDbrlT(CzsrxRpL7iw^n#Y6lL!eF6>2!B1yrIjy)kBwu%3XBHGDfCf6J%1@e*#|MF3V zYCpy>YTh61X~BDN__*Yi>9q7>mJ@3 zR$XoU7xDDtKg1J>%l552!v*rUv9$686sgtM;Y$j&rn`0JwnP2Q?F`dU(Q;U$e2xs@ z0cMd_=+esBwCIARJZ&yJ25NaMcR~i$iTt7TgP;`C;h21MCsSy}!FvjbwC?QUYd|Rb zTWnSe%jL6V>ZJ+;xqUBU*Ne7#H7S|%o4~$tQ&lj66*pcpr~DiyT=KvX*SYNI5*(is zHj<7D!m0Yr4~2+V7AC%Xx6dGJc~2H)MQu?I4>xhI8kTRT+qHOe3&4w>J7;tuXR4QH z(RC8&)>n_l0#6zcL*TfX-mi(2YI<*3M!+p2Tx4#1N)6vQ>=gNM&O?w|U$7z0 zVB7mgLO}XI>J1dh-{`y0fS)f3BCen5Yki77Q;u;V;jnmGlajYyYKJ9hMW>-6 z%5)L+g>3l(BaD6!oFbfZoY{jjA9}^f{KHbDLffC-Hy^|v0o3eCj4|-TADLOUMSM3rLpL~k$~JSKd;pLFPOhqT@XY-W zvd@G=)n|Hc(Ar{IZRaN{y<9l>$Q!V0R|ojebTtdz3vlG?8zXdlZ`sE+hBTdg1vE&rjp)9S0r2MZt6qfZoDQ%@UX5({weRF#s* zqj}{IzWX`G=5c-S6MZyJ>07qx7J;uMtBY25+wqK15b%U5pA&w5NYt<3CYI z2UH4hqXxwIHHw3+UJ9Ptb`O#v5(8P#>32;6$a%HJa3o#eFV?bQ;Qa6rU;&*qTIXOp z{VW&`mpGuV4zUZ{VAuT8f&rBCgcH-3g;LugQL(8wK|_HX^!nyDF-vXYZaNdU4*Pg- zmKOq8YtJUwdsueziM zoc7h`Zs}n8zAvOgPi==;CO|Q5-UDSzNVsR%v87kDRpvir*3Om`KL$~25gfRi{`S$9 zVBvXG*oJFnYW{F^eiA>khs4N^0Q0ZMKF1vLEqyq?E2@_Lf|pGc+;{Nv+jxXq7)=>G z{?gUyKoGBFQT!;R_Kn$3$=#>T(Bwghz~rIFW!1sYqv(8&ljsi1wbLg8SDJRyaHhiP z)3TqX_v@%@M}ykx@%M>-%_?K!#8{N^h`pl`&OoRCu5ZH*ewW)V#nUB^{!GnC8bMsK z=Ek&+1v@8q_x%aLI+BO0c1Yb?gijwY3iWmOyI>n4W(cZUyo!*$hl&UTUNn$8HDL`#HI z$XNEXB_s@AN@5{Y>sq_XdjwWL1)87z{2~{ou#Q5*i2v4%jZgHU@HsXR6NoyWQ)(xq z9criwj$uwntf$+-mRdlO(rGu+d+E6+Miv!$f;}EQkNZ#wf}qP|xNshIIO){wF?IMLl8QFlk#DVk0E-e+*#fI7F6wqKQvV?`K|Qx3?5>cSw9KWpc4!h%6o2&DRuUYG+m8=yMP>V zAQ58)>{tXjozLv30HPZknn)h>cXE=IVOxJ*OTG?D3ZmI6OGm!B-2+b+J&BS>M&W}Y zo_o&jN2O=c`9p5d)C){bB+?Vt9|gWK1&f!5#v3WuH4p2X?R}pA$|?D~GWdfW0+*0C zqJbX1;wbikSBY+66f8Lo*rL^+EC%M^P`nL<^TT6`SNrbT#h)6JdY z$I73)O8FgS6>$SG^}0_g`@YAI7Q;W=Rriyc(denn<&JnT>~p*~o7bwggwGuwrrMW4 zV(UR{J**kbc{1zh`*k;=$FiA6XLc8{dH}GSCmDz_sS%G{g{u`VNJetF*f50B#&^Ty? z=|sdhBK}0XWrY9bimCb5)cxTGCSqV0F(=>szPSU;hhzTBhtr0P&i&ytg~ebOImCNV z|74Ig!Cb*g$7?RF-yMHp1WcGMIDvyn^BcwRg{-CaKYIZnO6%`a3)6Lt&8}N9{o$!{ z&?>sT*!iM>*Pl;oKl2Wc`iJ%_-l*6n?a!}%3=$v7B`dL*lkL+ksIUlnvn0zACx|!d zG`cy~Qwe}nm*-p9WDy9IdL&?ooz)m1=jFZutKAx>gszPtTDBz_w;xHoa7FgG7@iY z);JSz`*ACh^v89kTE(p*g9Vu{}?jf)yPHu4hUms;zX>!TGMCAg*G5ogw^mn?_Q z=;fW%mkJ2Nl!R!bpQP7d>O{Z7--n27wygKxxS_MNOqKe~6ZwO01|$Cy4f69Z(?M<& z)I>iRY-wg6%r6YC2&D)Mm`U266Pj4Ls5Pi@wv?P1oMXhH%)E>GGc#X_JnI*sNn=d(6)LznKSnh0 z@gO{YzdVkwpc3jE@JmWEn zCu*lDt`cpw4Mn@Ri?~WbgCw?(JN`Ca{r3>PzN<|m`a?bXh5m|rST<2Q^dvX}64wfD zRZNP{BUQqF;e6-)k~w`@lzr<=+md(NE$u8auJT|>fx4B>gZ+hE)aEN_s?3=1D$IdO zyY4k!TVqIP5AF{&Wvy1?nVp`Y`MWn<|2HBF+6=6V_;zX)N2+8|C>rK?WL30H8}^*n zTPWAt>?=xEFnosYknMEKlBl(BjoVHSsyj`)9lV=al@rdb4;}9Pz0KEJv;%gfaw={) z!uf;k&S$$h?V=xtr+rcFcw1GVb_jk{CfwP!PsVesFzP6&C*nDWj#$kNm2HHn__PL| zlYu1jyu!G%suYE_>Ua5ve)`Jjfy@%rU?M78&sUkIzmI`PR9N;LCmP3tr>K|K>9{T6 zD;KJ4VqB8}o8dje{jxvP>z%E;b>q3aL(qA>jY?1GfLii8^RrWa8D`H7q`~7C{@y}7 zT1noUPXmN>x(o_tj~^n}9S4Pmvc|u^ead>?D6JcVWST{d{sexHH;Ge1g!TmbzM7Z4 z^Bi-N3yai?GwU*kCg6XcTXr+$W zJxa)upVa=wK{>n;;W2-UAjHUIv+b11SaY;*(cpy10wRB*UgFXt5Dvut3OCvbQzd4t zR$aWN$>=V1vff&Oa5eEruHOvPtx`pVs_}CT~8u(QYWt=|53z+g}~yulUyEW z$faZJ+)y7Chf5>*J+Si&`w|KY($&F#e2Kr}>1wB5Dh1k53F2b6>aCi)C24Q%n4XXA zGJ8?buOBF8Uju|3b55@BQyd;D;Xk#S$6@@pPlEZkPqIgeda_CRy}-+0`Fyiycdsxy zetADb-DHIj+H_~1a(mCo4nM8pr50BWVW9~$I#dcF|J4hk- zk&RS{3#T{9NirkGOeZSV;2*k&6EcX<Fa-U8eM zdZ_t23J?BQqW{tEg4*m3wsL79sB#!H-5EmBt?mAXPB4Ur62T#;aiQVmjV8`mA=E-m zOclNRcjDDb6Ly*}#k*~|tLM$8{wz$2{^k5#x8LiNjV^uwG(_Kd>tC?B*EvUgJ#Pnb zNd?`|EPZVcRL;|nB57c#@iuW~WH1GfVD2nk*_P~>g5ZhW%zRnIg-CCs>E8Z>5MB$l zBVhfVurb1W!@`9svu@U*dP{qLc(_Rs^$Zy+w#}zTDH9m7;V_x`KZ^c9jK~q~ojdga zNz_AOupnpB^s!M8;SYzi9^^szrhOwwyw>Lh@JHmB_U2)c#X~qglHgl_kXAd#4vNEG zX_naw9Via}OU-)p6RVuYQy94Ul`;7qXlYzuzkMCW5ng&TtGV|vc&R1v_~_26{Z#!c zj4oC_Xv2OE+q-70C^3=x8e@x0cD0_BV;%lRCM@Ety*m0si*x>mI&dbY@&gKad&!B; z5vearS%3~5GOZkd|8PJ)A&cPr*mjxeWk0=3hoYdRsS5?^DjIs|A1zC-ca8qFT(RLivg7i<&GSox3b4{q)Q=_7{0pofFZdB)MyS7A^Mp zz1A{ajyXKZSIk>p+srp*Tw+EPjOhkFH*Q zc63YcYxP~o3QVm>NQ9!XzqS(ZSW(P#-tYT6O+0w%@KifJ%+5pEYrW@B(%O$JuzYKB z7fNrST+04@sx7i{jbErsH7`_0soLM@`|M^a!$5lP%}Y zSmj-<!jG{rteAOdwO!(5A;?@oCw2*0H%N!!`k^ zugi_aVm)kAsWR(E?Q&A=o7W3z@ElY@HiABqFbC0ek&?S+DKuyy17JQ1|6?ETwL+W# z-~3%)6!8U%>G|kUmb59M-*%sTN1}f-psQ=}MP&7}TriCKhU%v9@2c1Jwvg9TV<*wT zDwDePbOIjF7C003PK_6$E?|1jVE*`am#-L1%uIamQ?Ic>*=2v_b6}hOm7FyzVvUA2 z$mda9Dsule%L^k2pyrvpC>oeDbH&hqsa9xND6aco5!07+%?I75ccvd(bu&-<_Wa!M zCo|-BXOO1zODvnkFLE7rVN;=8GSDDVl$$2+8wc*slKDCcofk2_pKMw^Vi}u&9!z>t z*IZBGAJS?hZN5i*y)05VcEjtEtdXzXl1Fl@(|61v+Pwl|JeWK?DYj*Ycj^GVS?3NGM>79-Oe%Ej^L7lxsKvWK+!%f~JCAWR5)|P!xO1F)2{TPkj~Ph#Y@1=Z z1?MzI3!%xS3(a}1T|>fkV`Qc*_j(MCX5riPyKlO=$29JBV(UJ3gR8daPu12-YCKFr zS3LCNUOt+SgG5}Hg1h@s;auP5hwgSpF%}{5-25*J36iSKT;KgZ?X;pHe03sRbndjV z*VWSsb)7EhR`cIKUZbL&?&5s;`C8@EXyjG>AmvrVn2xJb(OWB2V(QUgZy51hfBl4sP_Lw_^{nEH zV4-!W7<_%9_4|JR6x|MQo~~tpO+&^#%M&zwE=m8{5PAh_?-=BID^RBhk7ldRi>k$Q zTBz%zDT zHjpblYn_jEk2p;Rkrc{M1v$n>fsS!6m{ARv3E`Z@RzgxRVDH@N4T%z$@QR1;yv#f|Uv8RuMRMHXIMUXs6T~X1(tS zRqI8*F@(;k86KI@&a~?fol;i@q^guJe-JDtU7>@_WWgs4zGuhMIy^j!^np7gZw>Qm z&D(fUJ2@D2%vS{8&u77V?sZ}U;XQ@Mj{uh9piQt@-r0Hjzxy`nY%av&WYVH{%H$PW z7~PXn_d1V*wbp8KMT`fUVdqxTKoUd^^JjbZd=!}jmBo<#JF?s8oSvRJEP&?;A%={tfC%8{b31X3i;qrB z%aJaxtg1~BQoiZ=*s&JN<*PZAV|}-{Gfqn~dvy2O#rg4yfB_T)cDGbey_ag(ho1k< zY5d=^nWhD+{=jzp%kx+92?E*SzLp9k1ZcTYFU>s@_ZjFjVqrVXk8@$XRg?mbB*+rK z2;Ppv<8@er9c7cR!=drl9v6+aPMek!0EB(BZwS^x9(GLD8DTbia;Zvdoy3D8I3)bq zp{;$+)?T)c?5DY(eW;=36QwV42!4p;ZApGSqUSL@5T3P&=HzJA``G31VHN9{y&s|t zmrtN|QB2I+O*d|g0M|cpT`K3h_}i{?&q#qhO*Z@Xd3odd?$dv?>F^NxH+#4ElQ2UI zc`F7hnkVa-Kno3ke)fm2!uhoe1;w}T;<^dczDFObM?Uf3fw$$GL zWbyMChbT|Mb1R7WA#YY5#?*y}4_Cm zlHH<7Zr8{O{7}p!lrUanH^%_fH0g|8b1ZKp^J%@))V| z67`{vq}l!dW25;&Jc|pO?H)Saymeys{`}AX2fo7t<$t0B<{HIbhxuPWx6Kj!*_#ew zn}h54pO!wZ8%dIQC>?@FzouU5zx;e!3v9s_YA*+(V}H=;zkFWXhg#|audH`&7i|*w zpY9s)9S<2l`GAcCL+<}bBmisW{bBbC((tf;b-#@j|CbpOWQ5(Ln7ojt5@NLY^dCLV zz`Hr?h9Dgg(>fww_V-Ma`!DtQ{@f5J=e$5W1Lw{SdosO4|KCPo0K2zyvP9@1a;H1a z2kWvdZ`Su;ZY7Vi;pG`cJ`_u;>M1(*ZG23u#N_qXgOfaJ@9sFP<5OHhGP ztftJ>JuvLT+9xf;iVe6)nA3pEX(5*iA-8RP&G~kN1Z1IZ3l!Wh>GOqt<$mSQLu}EW zB0_oRD}7({Z-4c6bG_xS`nwb8){I{I%BN=$zDYX-(L;3TcmhMM(b`_E)cxl5IHtFm z4s(tSEy%x!h)DC?rESBY_L)Pd5)3r{gh}$7kS;Cq$9bIXeCDcrZ5RdMT{OvnG3;I8 zZFeDVmylFiNP7jEdhYY+0@C9HxKZw~#C)+ld@i0|YIR(=qDlYOMzA~rll1Pfc{<#% z2Z%w^V0#c9IbF}<1}#Y0H5Pi|V>Vd^87e@62=j6U*;23705{MCLxVKfl84S#`plAX zL`uR**cd}-N#asZsP8DXr6Z{S+c)*h&Yo2~1D31HAD_{TwoVo_x~?;t5wdk>h34bf zvF?O|u|%K*@DR&q#ri;ZA)d(2Z>BqKU5I2r{YkmDrJ#`aP1~f>H|J0{NdSRgZjy6H z=;^*jH~tIrDpnSlpSRU{w7ZI8&D8iP-n_huRlZsO_LBaGI6pDjok0eeP z^g{y{rDHhIXK;_xP;2#}0~?~x!@6a&E4kk$IK(){ zD0Eega2JGa9zY&}a|b^=-063Xu#R zlJ@IeCv-s%<{tqsy_?V?s0URyp)#7&z=F-0V$z`O35nl_r`@@hBwVE!xD#>Nuo0c^}g~ZG=3G#tF4) zkqP{(6IoGHOUvZ(a@*l%ZfNBEM!vEAf*Mj4*f%qsEL1X(VoajLby{bhSxOeOhuc)_4So4lfqA%DD#s{PVS5AJ>F$9k6!Y}RA0pq$}Zto zZ>G}MKhObx*Cb%6m|rvIR;3S{*Cd1mZ*l3X9%i3Cz-&v?`fLy) zlG-irgZ^A#8esc;u&?wG{KCujW3DDUJMj)j^a2WtXnn)& zXF%#mjruJF_gouwbNJXhM_`rT*r9=$f2~_yy@i49@4CJ-+Lz%*Ew$14I6=q3D|qIk zOC2#_`>*cr#D!HCgp&b?3tJT)>B{TYS9h;LRvfM~)hBCK)tE_dh0b#MzGo79PV)Fy z?^1>EKf7wT6y$)!ShX4unmj??`L{+NZL&y|_^;enawL(G9T|D#bT#esv-0p#NX}ee z@ilz7MMd1>>(?L3MivZr_NmxMFZSG9d{>XT-yUOA7_5{J#&EvmfJg5C&n$`)^EJRU zt>jRQ$QA%;$J~p&Q@l>n46fZCINdwkvU$rgb3^n3fIK3v;@zL@fP7&qkNqFqxppr4 z?iZIDL;p5|`evkYm2}gTp;_XtlUfk*ZZ#h zD@(g<-Q(xbca%)qA z^WLf^pC zp?5RXFI?i!ofiu8Bc*1X940bErCXvUq5XAi;d;)$WOVc3omhtk0Q%%1!R(F0Q2Rjrl*RG{Fa7m4#x&bVE5ix_h?_yd5aVAlrYrk<#0S4}V2ruT{q#to zUf&J$aB4fyx~+)Thvvn)xB#=8?&{|P)Q{^&tE?I^>y4tSUGbX2MKz+s7NEO#4~1cy z$cX=rar4RxKfhl+^t4dc7bN9ghaXJK$!6Q;ujKIWc|AHkB_WtLQ;*eiOW-L;$99 ze%ROX@#T`i>w`J--@vqP7m=+6HC-UDNYm{|PM>(Lf$zE&i5<+|-FFj|8<-|hkR_T! z)NzB)ztu6GKpG&guh{W4tirEcz+BNV65e2&dJyxZ)<^Oh(3L^;r&!u;3G9=%o@kbS zcM>l9z?SCKc(j3bjy=X+*f&RU9}?|7_JUt$L+ehr{U~`~$oJa>eTC|8)dh_aeedY0 zqyXN+gOK8%X~S+8!EVeh9JFJiE5HyhK17oo$N?7B7}!~?7ahsxAwl{1k9jO$JI42Z z*2<-xID#np3g8eJ_^zj{hQ8Lq8sx;$^1Mx~ni+7gxgGi(ziGW|*U>IFAz{2Y-)mn% zHjS{J`lecx4szKg3m!N3ukDvcLcUaPI?5%=a@tY(`8+6wLFH;Rs=PtlNgGmv`uP4_ z=>{GS{eK<~k8|ii8s*QYHlAnYFA>|wZc)FEOWx^icL)o8VB&7MHwC9Ihj@R2JcoMI zRBhDaz$glFP}91L4o7lAC@apA!f6H(kdOx?T$2B$fLgAG4che6FN%D*)bi612ZSV| z=y8Rxva`T8z@NPOTt4LYM*fM?0Nq;KDTly@L<7MpOcu!AFM(UcexEGj_3Tmf_ZY88 z&2eAzW8Yz>U$}Gv5#qPV0(}I$NH<28J`Quv3ZV=Gi?1#|fZ4$*3qK--ZX$X;^j(NQ z>cEtV;J$av=wZCzlqC&qh6+afxi24c$+uVnP8U978Wnnupn>6CHOs+PGz9a(I_X}$ z8(qlcRhCybK6t9+k$yEyR29t^v;#T+m7LsPP#ORhFB&eh8$=ywLH3oEGNgy8EL;#L z8asRtrWjq2GFpi$w$y0i!qirVl$65y(A%T}##?N56Fr-_p808wmae zVCrA2*n`1m|Lf`o;6-ZxR0iej|2^5S{d&LIGWO@`FE{zACj_?KUg*!}{l4{(H#QBKn7+ zM03?TZJHk;EMdM#;7AjW|Kc<1jxJk(tisqE`NvcFUkONwt#@{?V>RF#RxX@ z2Fv{;aqGqsM%VGbU(t^`ef|vw4sPccxn1C&s$l!ph)m0jl?^)4+nx^AC^`DaDPRzPPJTbznP)Wp0g>X&$w6uI={U&_EYgGc(^Od-cu2p(kiZ3 z(SJr7wVv2aO}V=P#BbyP_WoG>c6O+e@0~Fx3D3;PDa4mNku-ZR=Hih{mRXG9>V?D7 z`*P$1H-2{rSL8J&!-4^VXXt0x)5i#}E$ei0{&DJi(J56l4;>bu3yX`RUHcE(qeN$N zA4$V8rooAB?URV*5&D~w0+(NVPVkB$gk+~JSPizN%e}nt?_eh5qwIAGK_T+P3;l$! zua6BZ?lMp}KdVRb#2uK!oFW$xqS;u|_AqpB)Y{Pdq@ND$+r5xcm+kDb{Gcx|W+>My z_MF2}i(_r6my9e?UB=qeB-q!L%syeOG=!XZ+2AT5vSO8n(6XdzIb%`E?)`ClAKoL5 zLo^4f_C!@){6Nr8RZ6wqWRCf^m-&v}!KAsqjOE5*6b2b`9`bW*s?+>Q8A*n3p^#1#aEq=&wQg#|uJ!*z;Ou zV%S?hG`ahTv-1wRu}B+&(qLq!KY3S|m|1=;y$-)}`kFNp=V8}YOCy=k9ewnTT-VGf zNZ4`P%)_jlXd}N$sGsO%Lrt?$MqT)zjhsi1lA+8nv%(aw5_L`M31XF$eD-H4unu^2 zSeDznk)u&Sy`8hRpRV_Du~EU?a(HQ$pJj@hT7umy#;p7OdOK6fHN0;WzWQbb9C{{3>|y#Q+Pkds0rdoDi(|*m z;9DMTQXh+?1!*1CG)uB_Kid$a-rMJkd4zb;(aL}(3k$z5l6w;6Q%F(!n5kbjJ57|e zxifUlUnwU#UmSWy6HFTzw-9B8Y8SY^nS`9@p)sw2+9p_K@*<1Pf;YFHgRe~(!_iN`|XdtK+ecGAIVMN<06|I>@qyShC0;)Y;wC?_O#B zB><^A`T_ZZh^eB|x9eWyofE|Ll=RP2rk)suAS#wpid`&(L)I8nNX-h)G8iM>SooN<|9mO%a z2d(s(?p!a_^EedopM=m2_klzAMZeHSoYxk5Fba@Ee&^DH0jK z1=-3LV#dbqqrNZhS~}bML{q>$f&*A_r!PcTYwJ>IEL~^v{LNTmlf?J-v}@cLal7;} z7pkiZGcvdb!|p0Q(|x?Q1tNZEKkirP9lNH6RcbxE=H;kO7(cNo4>f?L0M2F)%1wPu zWs496IWrb#W!Ns5U84P2q#8^b7#ks(&b@pfW2bA3@zf{ov_#--_(LnI*`r+o=JNd1 zO@(Zmnv*LJFD#sL3#s*9_8O+nlIuBoDzWI&_4&4x^E>QjOIbvA488k9zPdFD=^!#z zly&Bg#!_P#ni6A>^UMVe9En(}Ka>>t5?`310?pm~%g!6w!{)_QFMkKZA4jbJA{j`G zE$>S8uuk_Y8FNfoc-%6WB-t=U@1(#~enm~D3_;D0Xz^@k41#qt1Vp?`wG0+hw5sKX zP$lvp(RZ11CYoPE4Jgkk)0HaTM=_8ba~4y!K4X%QS+~pji;>ZvbXOzeU=G4K`*=B8#JIw8{~C8V@f>}h z9mRT=zr({VEv+P~Fy_vP+@)-JlI-Dp;@cdExp? z+)jeyk-tjrbgNji!`s-ACB@rO`;|g*6r&!ZpH$RG-nNqEPYaQwG73ejqpcvjKktLn zQM0=~W#^=MQ1W^7L23^f=EU0}MQ^1uePxW`5#P@|Ldf`WiHh6)gvDFni#qW(C*O|2 z*YF)V3m$mN>8-PJtf@@{>Yefk)8b{E z-W@p{c`_`Icuir?J)55haX24r+UU+#ygRWuh8k>a>UnLhe~Z! zl6i+L=v7p0za=UdoN1IS?xc43l%p~kE`>)eR)7Z+(5cQGNSnD4@;-gchWvbkhg*M4 z()!los#NB%f@PYNbSDtXPM0I}et&{wAz>VJ(+$RMZYAxsjRWC$)E=U-)6GPMU2Ps} zb#7#+X_Tv~dqc}jQac#tm2fVLFXz8|y3B8)>~Q@)ZR>NlX{a@BBiTgkA1{fX);-OB1 z%Iu2fS&uw!=wJDY-u^0oIr<5!AZ|EoTS3&G4Oq5C$|mwn0o>)Q1*w13h*cWmUz!P6 zS(;8w13CVIHj_iIQ1mOh8s|H*ROd%5dKoD#R;UN*McFTp_dn($k04G&mjvpg2Gv1d zxzm5ji0GQ+aU(s`%Ims zy1{IVok+0BBv;&K{OUw=;fl6ESDL_{UyNn0vJh*2l-ICqaRp~tn}W+uX)*4{TyU6E zM5((m({)R}`lVTF_VD*<+bGAh6CIvGBQLwRQ25I=S)mPF8BowQt|gjh-`2%XN>5F5)-pQ#tGjKQsc{tRcD3(-9^-=M>gM}XMEhnEM5 zM`WV_?#e4b{L@9`L#)&3*y{riU$nqKyiQ(-%eRcl;5?bA7=&yH;a}HZ)AD>c-qJ#> za>S51HiR8%*uzCgMvNgPz&BV;AiQh$R9nMJ+{m)rdJr^uaf1V?LqH2J#e;g6nV^bb zEd;gO`kTJ=t?CFD^*7|>f6S2kEjz79IV?SZs|3P5})p8!fLM{bWrH!5Ti&TfG=}@zK&cW2sEukod&hG6usj2BL zsqHnsOz7z{?w~3dQp24K2%g6zFxM0&#y(ExO*uoI3LDSYq z!@k|KD4i%>tt+&1Y@ZL#^igxIe0*94dayJniv2szPom1j$t^xt$VW|gSaTPI?ooyu zQAL~W_xT+WjUqJCHnK4@-Lm)TIb=8J_*hq*>{ol~j9)~~lWLQZ@z`f$7&Mm<+nn8H zNEfqPbFcZUs^8Nfef-}ws8o4m!xBM}0-5+7M_w2gt#Di2-eG=Qz-~Q0#M~y|GXYv> zslP`8oM2C=!~G;o)94yDC=^{NrA4VD_Dl}21@rDl3db82O>>POr)GQ0<}7qZ9^APC zZ2D8_s11dcx-GX)#fW6}x*TUgiiKq#15p*+QBer+AO}i2?A(fW>+?cUtU8If9XOpU zHN?gqPCmjuBCvT2mxQP?Y&?Qhm2&5Z$KduR+&ZcdKwx(umPMb$J*j15JtN(zIg|$J zi5W9)h8a_iOmoTm=0AsE-<|4FI>a9Hg%IXwGCa8DB5%q%6xtKCWee-My=dSns?u1l z+H()YUA(3M^in%n^B&^sv+~-{R5F z2p?oz61~sM6W4!~AxLm_VK)@9Ix?b4Wfe zcfT{7%h_KYvKqQYxw4EUn{wVEj(X##MX58{EThNABU96PBlruA(LSCJUMH+O!>k1% z8jxubM1fKx<5t7@LCM5HYO=>40n1mA*n4go>LuDc$4=M>Z>7S$*w21-@dmsBS8PnJ+f2op{g7MbsF)j_ZMfzY|phhv{ZQD zqef?*YEkC2@C?R#alrgtHa_+5@G>MH>mPWqyZ~KQ<&+b zt~6hXo~%;NT26!a^$OJo^*uQCUstD)`;B?^5iy6mOz>zm(?$Ns(;gci<0mpJ+G1#` zbL1P;CW}^S^LZE-R$f_kJol%LtHcKjKkp-F-LgI)JA0j1cf>+n4aViFTms_Xe%sZb zzFjwWu~VZV9r%BEwxD8y&`NV==r9s{pqj;0xZF;>(&}MyGZK|HJE1qrsDj$WqOegL zWQ3o%xoI-%u3FAh*l{d}=3$?NlGuof#dmeDw;nlG=8=I49b4<`M9JA}B>(^)i32w` z;&;{>m7=F{5MHA#+?|9g$^#`xIz|o)qkNpe1haAMZdtJIIqWSEhj6nLt0>|)KeO>D zLDrLCKhFwFbR&>#*340HY1w_j?ia9t8#0d0gddm!s?fGfS$Z5Oiv*K&kIvj*4Gohdnzbc!FQ#jcszVw{QJ_-lrG3P%G;WE%Vpu zi7ZbQSXbW%avSX`ST&kAWzWOdw_~T|M<39+cXhI^!HwMmNI*Q_&9&Z_YgA8q|L!q9 zdXd)N^u<~%RqI%nzaGEU0F$hdJwpQ2a4~%IY?HjJQ|}reAGz@w~{d8LqdiN2b?jPw`@E9;-S!QZx^!33tg0R*ryg4nPV1|%-qQeeahR4*B=5| z0>4wZJo=MvcLAImzo&%m72HYEATf{e_DnuI z@nUu`MB#cTwZ>-}WM7i+iGMP>!g;Ua+K7CF@R3vS2!R3E#$9QgxD!a5W5jg^zu$Ym z(=A}vDM{F)9SO-bgn-To!NekX7cdv0II_lHRXcU|-ALf6?vN5 zp9QKo!HV&!H0B<&9ep2Y3_@X^lfU!q;UDFtxxd5MFS^_UW|{z~JIAhnnmlMM|KY)4 z@ig4@>EjUNndlL9Ii`5Oyy0K(JAxo2N~%K`axmUeBen>IJH(=HLqcKED9v`a_|-w= zSSbj_>9iw+B38Lp&2$MFlBCs4Gf^iKS&nHE|N_i z`_q?*gOBpD#&MFISSi`fGQp^4fa>hJuTtfDgFR`JEQX~ceTH_lbbErJ89g@?57of0 zq=CNu_{)FCYIW>E+f^RFj~T5Ri+N-mx>}35_B@T1GKk=ZA8EU9X&~k9Ce{Vf&iusv z1CQQ2r(Mcv!k_t)drZ)Lieck1M8U70&?;j5;`HIApezqi(4d&Ikhd&8V5pv6y;LY)+tgcud99}pH& zv~n9UDx^*MN~c%-Eak4>D!iQE7CbFZM>z+K+&l|;Q5?1D_v#r@%(P|=k2CeYBgwY5 z5bGDY>ldcoq3L5{EVX-A5(q~*KZWygFw(9yjC`k^6$(L77>u^0d)DwtL^jLd1V0PD z<+)W^Aj5o1-z-`)XZO}L5>WJ%P2GJ1pLhncuI08odHDZ*!QzX%-U35udyjX# zpkVgEfw1(LjP2bG{!5{x%PYbJ7Yg4n5f}o9XGPlpA*V}Zq2c;C$!wRJy6u{gMTaUn zdT3^He&YDyU`|@1sW?XFG>qNW3nVbIW^t?Ya+7^py`?TPFwU!FU%G~>PukrEj6xb0n?XA&ATU0cuF zaz>jND0mavM6G39d^=+TW!H~wL%lg_wZUH;_-(*bzjUgq#z?vI9(J7w6baP^F+RMW zE<4#02@FLHQM*P`pmrJD_YOc}owE--nUKGCE35$(x86Kp|2`j_U?gDaZsXO{Qu=#+ zVe~MBV3Oa^WOxJZK8`GKQXt^dT3iJl-@`0Vfsn%x&EG5N)@m0m&DdmV#u@Sn@$;S{ z1jThj?H8pmmg=UR$7LK$wtR*@3Pw{Fpts-jK`c$N>_|p&z|m$L9unvWMW>y*3^)9? zZ%KflHel1gkCLRbVkYCWw3VO@{4O=ecGlRt~^weOt91#+g)g{_VK3 zYJdxUT)wtl+T+UjZhOttf{wm=#v}OZpt|j;V&yXe>?!(si#_VQj@r%N@8#_p^cXRj1D)`Rx z9`zKEzB02;2QFf9-cnzh&{Hfd3hxaZ#%(emh{lZ6`WAEUzMfZ@9AC%Y0#K7TwL|zS zbh&v3ZsEpY#{@lY75Ku*Wl=jU(}`>~x+M4G2cO9|RyX4ge0?d*`dviXjjS8KUJ^E? zEDA6;Dm3O4=f24WBEWY?92P>xs@i&%@!QhJ5WvqzH1Tsg9s!q@e{$625~2yOeh||ogDO*-e<+6ZYB#8Q)$*D8 zrj3*$wj5e|;(uXQY1SO8@l=W=)_5}6*^{4%DlCZ@I65>KfqRBqZ0-}fiv7t?_C+$b zSOjlxu8aP$yC*Jh&b+rbS^4Ra^8{7jtiRU@)>_E~C$Yk7 zi{!ni)M+ie=@SMjST8Bd1Ys?AV|(#hBAt$YyPQksHTry_jaFlX&-H|_EZGv_)(6(s# zG0Cnd%3uh3X%xZs=prFmN$fbn3%4TLG(NuvXJo{ZgakC)W;>QI@W(JW#A3BgnKky* zBh0h%5`qDA+Yu3%x3db2`^|RR)O6G5*KJ&Yjj_@)eWtf^AU*?2Snhh|ic@o9cAU|` zZ5)p)K9o>06Z=8!3mfoD6_{Cin#?is`#Sf9LfIhccR&KP&`{`-y^(N$)Yu~hASvaYD{u_OD>0ye_KjrvW2 zeCYEnK~CJK{j+v<;oR*BK^0lt<(6j!&0bUF4FP1IcdJEmz@G@b z9SACu4JLi>&KG$wyyTanXEWh`p~08CQ<*}u4mx?z?Vog@M32N4#Fxw7x~7PtVcKo_ z*tmNP@mqznTdZ3*Yl8Pz@yJ}%ycJGIW&a*;~ls7smDg@tYPZa`JzaK()+gNQI~e2vBf5T z**Ndwd5zIfLUTs_q8O5tK|Hw8hNGfMu%yBvCt-FgD&7e&W{2RvppvFjpVtd{kLWc~ zc^nw#eHx-xsumaN7mS+Knk_v_vX#--+jArK)xV*&hCD?`ytabN=y z+4qI-Y_Gqtly%jqMc@1L=gf~4Z1b;VR7a?vb+Gg#9n0O6n}10WhF=Wtc+B*R@n*mD z=JOirl{Mg~zVqmKE@BDM2Zvz}3U2j*WKW>_tH()1xfvE=vyjr{rb&uvXIp^;C4@D# z*Cqh6Ug45`-y*X*2`DmgkAElgik5w6Bd|6P$q?`~hH%S&USiVl-I%-JCe7Hh-5e!T zZeWoF8!8(%c$zX1?*@GGV^mkDpxMlKQ&c;_E3dd zf^7JWyUZths%8uIJ~?Xp>!2NC^8=R^e!iTs<>u@KLq3}`;~f1P&*!NM(=Ui?(mK{` z^lvz(W_H6$opA%|MCsOweT8q%Z0(gr&ptE)1)pO?tz>VLT;~|DrI8nvXEIJS%d5R3 z#BS%+hsXDxt`RC&bYjb33&5g4_)If?Ln#y-g&W8ZTqyZ zP$jjERqut~9loaTFL5=#jUB$y=+D3FQT|JKCn2J2;`y#G0o~1q{!9@S09y>%x~e8KAg*3%(0FCua(Pua5G=0r@)) z5@qn|?%eY?4niKG_v^lD{~PfD#o;t~1%JMCq+63{7xS~GlmNHwYvNAPh#J4CsqDW{ z5A?O;F?Lp)D_EzO*UXjh0(sISuD`&aS}!`D6|q8bBrzviu<5xOv*i{#0`sBCjPAa~ znpbku38s*JCBouDENupMmz73=Iak7#NT?>urHJcyTPh37_tMQD@o);q;(YMS`O87+ z!n|!kCH8qW)<4x5r|b;(pI?O9`;Tu8kQK$=)y$18aI#&`wYl#jq*z<0K7rX(1IS<< zUftfu-yTTx-fQ1Syq*E+h4!z>e=ZFl31S|Jm~ryjFR|2k6@`zyxUt`FmP;pk{|m-q z4%EdNEhV1SpYj{=U8d|umq9D1S@H|N#*-C^o!OXBnEiRUSrRg_cC@?detW9s3{hJu zp_bl@h%;uYE$hnrKxk9ua<2_cY7-^j$?M$^8O>6Y15@wjG}yw2Dc*uNhi{740k3=< zzNqJPIMdQ-SS8ZB4%0c|=iT?gvIdiR_4SFR#q|;U1II@N4D;an6duVFRmBqlPB?r z^~acmZlS~)udRP!C!)WByrkNv0nA&PY*`%zncVG!Z;$W0GuEd>M2kyvo;^{KI~Nlr zPt6$U;<;^iFfR%WFSFh-3AZD~6i(-tFURyb@s~ZBrxupAx#FFGL@ck1T<8wp18Xvc zzi)m&vdeU0lodAFu{9lMqHLb+k;-5-0XLqvx=nm;0RY|GsYuN*bwdp4K$%hQsd>+t z<&g}tw{D63#mY6r*sNz}QdJUa;gSpV>?ZG#tp7osbc)=U#}6{#@_F)3Z)9WUQQ`Yb zBYQ41+`EzmOT8V4z1rIZ!QXfdt=UjWTsZ(o5w7H%*Dj~`*)GU%Hz*MF?B_uf zWhH_5kj@*C~N&F0Tv4tUIe_KO<7?GQy6ZH-1qx7rBe4CMHRY?2#Wh}gVkj;(We z(YZE7?VL=w|Qt2`Ueu#x?ftP?+gb=TbhS^yO zm=TO)F9N-~KwFbWv~-1y@AGqFqPEufq81nTZ@7i!BxuHx01-NVoG^%&GIchhJe;-I z_ha_DH8Fw81CQ=kzZIlP4M8LlgOPqmi|~HR(oTLMZMlTbesGXNVC;b+uk6CKl+maE!A+34zYuc62;Mk0+eWR8!TgY#V^@V1>bw@PT9)K# z%@Wgr-)%JD5P#mbJCa{laL-j;@*ThQ{e&?lxz+LATzN{e;6t;Wmp(Y(QLiK1qrd`U z{dKa!Eo4f`F$uOyG71%VwSiZpFmhs8(>?N|G0INI=y4Fe%u$zoy5PQ9bxDEJ#<$eX zpXp1TKMpJWlo^>km8g^f0HZ~;uo1^AK)8TAiRshx5a1}+J)>Wb=Z`8NFL4;`3yfKG zdZlpZA0P0^^KYheXFJraODvRfz6C3l%(W(5u#B9OuOvW<$sWO#`iOM!8a-cRCqlu* z&&|Qew#E-`jf%n-A%eCIohCWG2@?DrSs~88-^S%;QMj911LAqQ*7wezh{nCcV;Na z=yO#~0|PT^2(HV*yT*D`+B<9)He1oMPO`?x?d%%wPr*bMe*q|9^RPWIUY*yj?R5#| z&95xpFCmO2_(E!OnUW>8`@3+PkDL@#=+(tW#jYPxjj4vJT;KgXYGY&>%Wel1{m)`W zfiYVly9smXCY!&<0)s5qH#&e_r_U+?Ri=HQL0G=vMgp;5FhovSqa`OK3R^V0$!c$f zjQ588ViUQE5p)rs`K#^04-2{Xl~k82fQ9+Tji=|(JI681j;3N?@wu0dKTn|^5s;sJ zY>6OWSwFdRmpjqrQuxvmseMQ-MB<=(_Lq&l;B5@Z$>J5fM{u)v&@J9(oK+}Jox3PD zY<(SQm-ob3=PJy~$$3RU%s?<$J8E{FDv>9PkwukD@&_U(G#eOJ;-ND-IUtkx2v<^* zF9-{y0--u=EEZdjCC2o==-an}TuzD{Yvf2CG2hlz9&W6l=q!je|AJ30$}UBGgnz>) zuEPS1Zn3N@;QS%G4x>G%iO+Ka*{^!H74MHcGWuXsUD(Y{u{zo&#+X43-%q!}1%kOm z^VV+K&3aWF#pg7!L+3hKduZLpv;uB1X=;gP_k+0LT zf0HyNVU{O}%Xw_kq~Of^7p@VN8#@im`6}4tg*?kUZTqPm-+%NiY<{TDBS^d`%3=H2 z8BPhPh+FIY(Kn(Eb{d%W0hvmlb?;1ICJ5)(L{Pi0Dd5E;@5L;H@{0P;yhdPYvpAA4} z?~H1|g14AyTGm5h*5`ov!J+ll5O1uFQIP45eDL1?4NaIwqOHtYm|?HbTQh|b4)Q!W zsaB%No%)&!*7}@Cu#t8B@2po&j2+l5~I6_<1mbe0dNC zdDhrI&{=KZNJ4h!%qO-cqX)!6hcC={$FWc%Y9l#`=*Eo%h>{4oU^o7}Bk<3gPBZ4v|{)2ikksNc8?9SXS$eXY!4 zRhx!zCNn?&oJhCFj))II4bNgiPVN*T47dg_5fqf4xvzy?4qX;nOh8V>vc4b+K0^?B z!L(CaE{-*~&#ELjPw(qAK1O+PmWnj`2=P#xdw44Fq}lUAMdpO^V%B+o^^a`R3l^xC zuNge3l3_2IC__)7mzOfpduXgH*=>SG8VdiY&}iR`DNO3_t>AS!6IEIP8Dcv)cAeOC zG~AT|FzRH1PveG?m@ad8j-Qe^ENd1X1tFRKIB+7$L=aC&f`kzDxk8(4MwUdw*5q4O zq|E%o7q`jW?oX)t6b;mA%9@{t!uh`G!=D&kt<+pmiU+>?AHngzMwXvYcPt`f8_)Ur zZY81%pg;_wArWz!q4?#-oAq#*y#$DcHXs@DYSI;UYLh{--8+ta<8W!~Z(-W;qGZ^9(GYptYYqQe|D*`pPhEMX92gjfz(#U3@0SUBvJ z0a^!8EiAkPS^y!$XKCc*3Gy4j+jN)W*Nj?)?D(L2L6n#A8DCbFdp)2-+U#p;i{rcd zw`kOl%#Ijk_<%gjh_VIK9Ee;X3hfCcB zNES{NYq6?fq?&u(cxGl)KUc^Pukr59Z8|aGpRMevxASpynR+7cSjq@z9M&DGerG%( zM}q#2H-Wb**>nvFKRJ|?xt5Lh2}V_*Pb0VRJaC0vnwVapx+2+DtteQQz`-He^RfNq_N~tfPhIq#@@Ny~N741`+t&TezSPQ0BlG?Dch7fa*@l%Hvju^M z=E^Q$m&(6e9=cJ7L%TcaIEKmLH@(ad!ss|)&_5lcU6&h!JUoWRN_dU$;`(gSKFztx z&Xex7a?OCFigd$}icfvY2#X&$=rOizQB5fhL;S5kysZT&FuXEz!N{sr_*UP`9Z%gG zc+z$2_FC+(s;;a~nJTJlEOt^ZRde5f-GgY&rxUW%SX(&g$T3uZP2}|q9@wV^UdaEW z9jfeCGMBW9@xIgbl2f8?X>AZMHazy1=Fa$~PU8>qV_i6kUN?8S=Bq~?$9ebLZ*$M# zZ{8D20tjPG_X>%-|Hy$)5rTTxn4T8|? z$AUndnfW2hqJX`WSkxX6$wNEy>$5bNPn%`?>l6Jda{yZ*2AhU$AP}@U5Bwl1N;61N zI;l9)auM7Q;Si5DrD{WI7E%O@kNIp6mBy6T&P4epe`mBlSCK1?F)%Q@DpO%A_~O%(Ljlgesj* zljVG8!?zLq+>`RMCDl}A(LHejV%+@Y;3voZ$S}z^F+#E^w|r)0Q_JU211sV9&DAx# zC081B?!I4J-QlZg+gS}hwG@-Nd`Fw1rBMe!2xIP*Ag5u3LrW;Ta&x(prWF2q+Zb!i z_8vK1RiUoJ^bP!?@EOiD3re1i7TN+Kx6~`K;^L|TNpt2?t#^L}gE>}AB;ZY)`z6$A z)&;b4QJ|7{20nb+l2Z>`_yvAfbHn=?F!Lp4=>Jkpzk3<>#Jf9}&c2o@>DgQx(A0hS zHU>$T@9njUX&H##;{#IG{yG!k>D3s}-u%i5i8Gf7?bMI0^s^7muA~UXU5IT?1laj- zc@^^g+V+Vr)qzX>1FBD0ft8mei^g@y1=k@CI>n363hfe{7DrB+SC8u4$vyp^PgEDPb#v{n22*Szwa5 zySp?RB)Gd1+#$HT1b6q~?$&*nd1u~lGAG|Te^_g;-o2Zuda8C+-OqIwJV#gMS4`GZ zkKJHkrCqnMcHQKt{X+fF;7zJ^Kl29wn>kxjKM?(Y#E`J{l(Nz{=c{~^{1OXq``y7D zZy&_eFYo++nIH4hqE_0dH>)SG-pu@4=op!Vozq3)uz?DSg_(rHKtSbof><9SeP(`* zk52HZBRDIXU;qJuVbY(2WdYF`W!-Bdual$v4YPE z7)E!ko)7jZF2jk9(ptFTN5rAz>A51ELZ0wGmStdZ;M#dDc1b&({WOFDA@> z?(?PagdfE$;{q-g5rrkRanYn@ihe%SaP*>_x|w@L&BC{aRrlpuWmP-!A)qZC0McbM zvkV?I<;G^G<##Z)7Th}H5)NOtRx-m_<>&TMiAi`~R>#4Z^zi|B3u%Mz4AvgM&eqQ? zvhW_YBsb)JwW>?8Y7n(_*}*s(*a$(P#vj(pEMnDUfN>ZoeEqd#`O?D9N1~ch`$srN z@CuBxE*r8Le0CWPaO7A6ediAMKNJar#Xl($f)2LC=V0n+YBE56=V7g3!~05T?h9xZ9_^@t z_cg90K|fX36G_nG-)VqJOW-#hZ#o2O4JLT5JPmYc4!wkj7z4(uJwM+2RJbbdc4lN{ z6y+%zJ3NiR*KGK^hRqAn17f2mHL70wNcGmWpke6B^<8|GDzaH)rlBX4VPFNu8n1aZ z>KcBmUZAvnm7v)$|JFE{n3(sVuxk>%MdD%*pF8pu^&OxfA(w7Jv~fgUWnj{I?pFWN zvILKL$>ogsaOYe?sIW9I?9qr1Ci4(CphjZF!gZ%$cCyOj1ld~ne5 z8U(4-;!QC0Od7l5p7v``dU1ewto01Kd)wPF?I_&yu6sFL_pKm(Fy~d9tY{MUvL+oF z&ZqkSkwNh9Ntz7%UgPeUmhVg~H|fZ`>1CrnV+6_Oe&cKX(#N1iUW0kkbSIedsW&&r zNpA9kiIbsStLf&u3}mkj`G?O=-v$b`o=+W*)pQJz;#@KN77J#eI8#ZX82*GyOv7sb zf=l4ZA_o=Pe2Q}pIQkSK7PcOG@1tlCfqi4)%=7m#UK!Ya;#jxppC~ z!$UHza(m-hkNvW*bY(EhipswoEEbqvEfBs{L1mQudlwY&+b3xLkMyvO7gY>zqL zVk`a+oWdmkD1e{cig>3wBK$(9%>7NL%-82l-J85QCTrrKMOaJ1qNDV^->y4-iVx@x zZZ%#8>C>7fw%Yb|iq60~EJXUsM;%FHVCABe>#bzQfL^)X`~5*A1v!HUOgcfUzW~3) zU-{W0!fe{&Vj*y&7&l4fP57e>|9`ScBZgqk|0bKT{l8=r=q8rm$tK@WNu$V+CC&X5 zZi1oLtG_1hJ@~CK%BUG&5kli}3>r<1$7?kq>s~%a(AR`K? zKZo^;Fky7nd@hSueZTvzIWI3xZ2EEX*n6J-^a4z_*T6DdbV?1DqSblgnz1p>%Bcpn z#AqQf8%Jq=*g|`FNPv607Gtru5J;ikeD_$yY`>9-lBupOsi%u{)QphL}|?E21LOh>Df=6vtW=2>#ZEux#CsNV8&#mDz>xLU?C3yr!D<<_33Oa z3W300W)uLr$_7p>!Fj{;Cbp*(Fsi}3qsQsqxicAE}$>CmrnUYRwIol^GaxMxf;b3)WRqE zc3{fk5_@7a2D0p(0I}|Q&O6TUXO0hx)OMU-3mh}m3rS0_ZLAcNpMrDonZgNF!VcI` zhH3l8ZFIomIqb6)1%B3AMO<7MSvqbD9<{nBh|-)3jS#qLlH4Ck4}XzK*kF`+o~T+E zzDXZ|^}Kjm;q(Wx?>sKeK<|5*J+kq!R>TA!Cqb-*m4va1gnAJzLvEqedtR_edASxU zRZ|7gt6qATtw3}+i&KZZ?ND;LTVXTJcQ)Z z*rx5&0OC+WHQ0`G;y#&NhSKhR=1gwNNZ=JJP^;?9{d;J!tS37BWd&Tx@N!PlRts^P z+WcRljB(Y5!L@(=6yR|r`*Le+vYq$pE9yH`?5J-MUgf#3YAUiB5IPlOnoNcAWVmpdmG}0S3%u5F*l}v9o5ZwRBxyk zNTPj4&Z|AE1u02~?z>!f7lD)eCn@(Ok~~c&F++0BkNABYgbB!8F-Z63$m`tN%$*ijJGjiu!pm5$C!!Az4mntd$t*6w^p97GtK!e!wbIz@MnN618TsC#SWgI8i zFCN!c>9OmCDD!coQBfc;lJRTk!0i}G->wM{DQf+$=)nWECBri6vE?Nh{tS%~^{Vh?r&pMmVCUQ)hi4{d(c zbJRomha`x%>1@hjuABG!xxaZ~@hrO{jug$4A-}RD3(keKNn!TmRkf$p>iJZQCflM5 z`<@8NK8bfRT)~yJOWdT>luR6D5~s>qb+|`;6V;|@s)B2lZM?ZQJfjilXif%jf~uOB zSf|5D0kPWHNzyxod?zr`|C7L&YZw z`TOLGogI9KEmq zH<}Yr3heTrNW-_ZOhy5#9_7iK!g}Vwa7e?4GNKdx3Fn0FkZ_Y^{s+z}Pby!Us=>TT zURQfBLZ_W*Dr}^le25M9vDWZrdOnHnr*iHVe;BaKb7l3U z+J?*V*Y5Tg5DP5p-&Q=5?OVk8lC@>qQy<=kse)|9Y{go zq$-Ec<`pt}a>G98myF&x#I9fAs3nCKGsxLl*`CxNTsKsZ1 zIf54wa@_*USNAvDvVAMLcVh`mDQ~^ly;3F>jbfJ<@}g|A?I>}4jBB4*@=zI%7<5q_ z!vkiT>O&0|(9*dooe4BbWJHfQ>`Oe%(PoY+@6_IbjJ%3`6R|C~caS;U(dNDG5;1Fy zFFz}dN+A98hv3?chZWuTGU8!t2(y?le9M?($fq8&D&XU-iz?3uPsTE6r1>wv zr0jG#&;BnOh++L07vsM)5ZZX}QP?-p|0@YJg#0=HSnPj5} z0+KSB&feG!x?)_Ctjh4B?7yxSrKiC*GHp$M!)zCZ6<^{h73xkxoH_G7go!H4S&=fz zVkSKD5QC1p0`?+TZ#}D1)J>UPb3@jwe>AcWhYJZt&I)>uyc}zfK9yR(m8&3R z5535ARl^Aa;!>Vhv=891eRwsElfIrWoD7OyFy)#3#}dwW3=@ir?4Z zDXnbJGe1v+B7=9D=_S5ACbTSnsi4nh(HHsa_`R98(dFup3033XJ z=NG-U4AM<;lWckvH$Aa(6xk z-9`%4dv7yodmsNg`9*=J%H4e<>WY_q=rdpd_G*QVfbt}PgMlUu$(j_DV9XgQ4Wmv# zeZYy$#ffx5%#NP09x&E_*F#I7CDJKDPe*N;M2;}nU+Y2ML`51s-;IG3Hvm6qTqaME z03eel8uc}pG$4C(%Z>HJ4t==jx>b1lF2s%#^-~$f0Plr=@Ry5Z{lQ1+9cX0fuj2|f zd(h0VVU@Kx3gU$MJqG)bL*qNBp8fml(i3Qw5kFlTUSJjIO;A>J>W60BFR1P=!wXnP0PB<3QfIZke?mk&OKSBiMMyGy_kH}G&BB6TJh*P>0 zCy=X!(EZ&TaU;A;8pI*YYLOfTa2aVSuMT;J{s4}`Xwx9xK#9g>Tcjbusv<;hb8~PA zr@bk~Xb!wsbZK+%!<#sf7aQ$H1w^qb6oZ3{3&|6gCV~2@qFoPFo23S}VFe3ytQ3}9 z9)FK%3-ABE*=Z;Uv5e^X%?))_FS>3Sf((VU=l~ub!C48Ocabo?r^C@qWB6_5N$uDEP{|tN|=+RCQF(u$vd5&ipy<0BaK`$nmT? z-ezCPyPN-oFX}LcDu&@}CE&n`L)N?N4iRm3G~sBhZf{Tfmy!10Q3f4z!dJC!Dq z``#~}JOTkN$4C+%p%VrT>AcciWG5wJ&S^HQwHmI>zYc|QlKUvk=M$6>?C~3feP{g})VhLJ_8&EgDWJZK1xyW&;#slRM_#PLkqyPUL;Elx?UZ>lNOTMti+Y`O2w*7s9WmWB**&af?j?t^8D4;+hJ!p6 zA_ZB0&BHIX#I?3nIM&L1XdQe$pEMagKZHr^lo-(Pb{M{Vm8$>3U?K3;deNh(X;?f0 z&&FA0b?&~1EY_351~hI|Mee1hLb+W-X_w>xaHdXW$v^EU&d+MOB^FW|trs4N?veM* z5*exKhfk-(21Om=JoyElvO;no(<-!(7hCc~T=Hp|Y(ufu0SMmhn38d`Qp3I#M?W^P zvGz)}B=Q=rMI?=9i{|?4fJoYo(1Yf2=Jr1ED&O=8%kwj1ORF~o0?Mzo_lN=cXxv|V z@RpGELbnGCPy{btihK5^3ue|707^+WAV=n7KIm5|J%kg0K&bXkfoFw)kxWM+)}F{3 z!$}vxrC_ImBRwHNMM`H}BKaVbzNG-ylM6GW7E}Mwe_$+(%t<_7qgCb^=gYBOb0IDI zmNx!fjA{+4N_v?u)LX>u@6N%SL1|P|h8lPhV{Wfq$+)~tW$4g5CTFUGzh$$Ki0p289(Wv)o%ZKkNqM5H1RXlU(hP`^M%O z_PTowd#d|jX%=MTuJ+NM$MT^dd;3@E8B&bjEpJ0NZKN$|YToKai#*3Yb%I2z_<3la z;xRl&XJgy@))szeWa5s@x3+_H^Uk(@!U%UiQ4N;Fx7P0(n$N42j_i$iqzg_s^5;?y zQ;zt!xbcQBsrRiTIOEjM`C{f%+OB?smyl&*!Vy=z7-#hKqMMh~JS4AnJbWOGBLigBjQ`x-TTEbPi6;xTZ8F2S2B7z+*ySY2R)3U%PUq2TWKZbnu>6n%waxwjT_ZN++=g znFwfbxTkX!4B;8q)lnZwlkYnE1AShoA)Xg%C@+}kR1EOO!T)K)l$U$c^kbBl=e7`X zT*t}dPbwe%3t3*!d0r2s)C0Z&hor1O;b2mn8Z2sQ0Lk=-V%HZRayXAqT*pB+%xj-N z&nPrkzicjQdB}E%eXOPrKfg4USMw;=R+H(^aHH|sKen3A*74?yKg}1BjxY}jmV8G+5cmfmKLHp)Ro02Yr)u$l0SG@jrxPsCqrVd*TtOF7Sv(a zQD9`-667Dsuki?%21$Hfslv0W7+Dg)vsZ+u-fnpVm?bI@JtHZoBxFLwd10_YJQHz` zi(o#KXO^Dv(|=}`*uwCh!W(X>GJytU6*#0HRiLoZt3{vf6C%%%TPt#3-1|&BM|wS# zhzj(W-DheO3+qlG@y3ZrJ@Dw0s{}=3N-W%JTYd-sSbk}yZ;Se=bYAGN63dOg_Lrkt z)BP7Ax1Kgd!|8I6OVdp$1GATsz6K7oFADS@hHCoR*rMD_G2ysq@p966@035_Lu8ZX zyk{uQ?@&?filDNTGW{M{0WNcRY*6_@^IOs49t!cPYf60JUdk#DObl0AliLw{I~4r|R#2dtMMm`nRqAfy zBR@uQBSNPCqu=zdX^Atex+Gg_;u!2k|A^5O+c?85*{e!zODWc5dld6)V`s=}Tmv&- zx0)f889xdDGF^sM?vP~l%rwB*kU>u1IJ|!WJwBx4P`*?;*5;dAE4kEXiekI0%4v`S zG3nkJLMl4!iJaPY_~D#~RGdv?ICyO96|5#d$keM$P)x+n`XAptBrfVHEk~M=T)od$ zD|r|vM6}MI+C2WkL8QeQ#>wmaain-z#Gg`A%!$2vD;wl`pfD}K@osN>JtWplqqp9W zts4cfB$1mB405kZ5abpR{77Z?Ml_o=_s8!X(aBCeIU~^hV@W- zUd#(8Cq8^u|9^r#M)CMwkgaa10ufzrLba8&-QUe9&t_90H=cL@2p?Tbl9iL@i6v^? zfGRg&8WZOT5i@?Y_K!3%Q(vER36JQXWc=G`Mg~rAGsU(n z4$O^6KT17K;MeT0l6&l@6k)P@9l$SEZ4;4XHHjv{d%#MAy4grtRb%8oHwL!1CVny;9zEz2vnNo@*43D zllT@D7VMu@qzj*}aRWJ$c`G)EX#cSMBvM;r$WJR_oJQj|DDW5qWUcff`IN*`Sb9WO zTvV#>X2;o7i0Gux+`9E;=jt|wc27e@qv(R+NLqKZnnCYuyTB2Pf5gUgAU|mDW_MM_ zXzUBXSo)uaWw;?CqDx@N+xep^>WMEVs6VnV@r)b8|vJcSa1;VvIP9og|j*9U_)$wgx+YfO;z9L zPyez%865bvr_I;y@v`~l1>eSq&D5&<30z?oXZd(bD++G?a7lXCQspjfsPp@T1Z^0*`O#;&HV1AcVo$(Rx0Ubk_Fp*-hW+!CYt3xRHjX69f%RBr( z^q$2Ry~ha*8T+=achU!=5JL+*z>H_`>g>?ao(nVGj%>nWV-;4f{0!O9Wk6rB6I2^(tS3uyEUdaGQM za)8Vq)_-pPoO(3v5p}!eSy4;dis}jcz93^)Dk3;8ih$i#Q`euf7QiOcGYJP%yAXtgZzhIa zb{M#lWCkI|?{WYfdaP{%8OUD00u}5>!yPC5$=GnOy4@Y2y^v1u(qaD#W3%wJ9bk!y z{zGyhP_=NBQG6V<2M#>gSmY}M0{`uEZRx}Q6=qZ2QZp$70B4mj(P` zUe=|;e~o_-t(gyA{b&Y(A+jAF!`)i1pzpcuuk=pW#Un}xRh){$aohGlwdQNW>C7ke zoc{_b2%vLT`)SmKJYm5Xx@IL1s|@i4t%1qpwzXIod=ZOj270zz3dI@46TA9_D%&KN zr`8NTs!12m+Qzbc9hYZiYLnS*hl6y|W~YWy%BBtGXId2U9-MvfZL1-q&z?pvV@;oO zv>cB~FO5Elw?t0q6tuoNFQy&(A@#1a z0}_s)LV@Y|$`Bgbxv3~L|H7$+aPDTZ_UCTjyaa1+sXH}GZ}1Jc`@1(0cj)=Q%wdp4 zU~-^Gsd;h2j=mYA^Pf{txcp(0S99`001a*a*O!bX>sQ5Nvh}};$8Jl~Tr@*2mr2~z zRBUFpKrD$R{`1Yr{j^z6w3#qg&1+jj9oM%2$(=DU$7XEWEpD+05V_ghc(UVMP60C3 zm!fMQ&S5Xdx-Nw9ia@HlbW3U(hLg+FBGz3W zG?Is;c?vb;58zUt<$h6~$*?EJl$EQ=q7FpW+fC`h)4p?lOFf9sOVN3gYgZ-0>TsiY z@bOtcrbmNLNiSH`KLb_u=3-%Xjy=84aFcWs`NyfCZZ9!zACK)ryT=i9L6g{Q*U$gc6$nHTKt9;dGtneJr|)Vhx|P0hbN zm%LI3^q>U`BueUyCxE;gZpYq=1Amnq*5MhU`b;22r}a>G)lN7$`WgOo8N(CJJ>J64 zeX2W8i%}$hjS2&04Vj1El>WCDfIg!;UFys^?#~3akX)GLRZ^=zSsTuXf3h~uFRabN zAFPdm{$T(8xrY`_mYdaf<)5x&7+!y(HZ~X5&u-#nR8vP{+bj@2EWY?HO_;l)^W3G& zO3Hh|K(F#6AtN{1P%s=T`1D>O&4%sZFI4q)e!vFWF6ttP#vB@R@biu)MGC+nFTU>J zpf{N$r60WQATXIuJqaxOZWT+1+_4YccplVJ3VFSSYVc%d?Wd&j{;L5JgMeZfD!zt( zu82i*p6Ea+`V0mR6U7bjg|NDY@-P6}-v+1lp3WOg4uFtP@9Kg{jim zy^xr}MV{-2V!+anF+RCPC0Ebs%P?qJ<)ogyc65=Y0BZ(QZpzPHi0Gp`7b?IQzF7s{ z?X3VP)Kw*2<~K2=xsSx(r16q0`PsL>cuMMYg_cmertR zI9u|6ndN}zz>R4m!GNF7ms!c-*hjzY_S+7S>S`i=W&3dMlVN5VP#uk1itYwV+65~L;8dY= zEQtApWXbb_8sMrhVg`FyJKqul&UAex)srD!pLYlGtofta7YhPQ#I}9kFN*O-`uGbt z@Z%E02vVpKZKppO+TPu@+B zuP~t+vW`f4V8}Y}$NJkPZ(0IGNN5Um;RQSNbYS8~U{K?t)}d&iB>kkI7Wh&71dK*5 z>H_qPpF)QrNDv`mrg0$`V&+~BFKI52RdI5eX^o$gP_?Jup0TfYXy0A_ZvEMx<@8CH z5bJ9trm@!MD{{~b^vZ{IXWnVY{<M=N@6>Q2)ny@+G_8 zB_J_TSI*O&e=kE3T*H+4vvW`Mggg&$1p?Yu zdYefi5uxr)&x#@Q*RYSNva3w0D*fG0rg{*!+o=$Cst7w2h)+t*dpB2?J@o>_NDLU- z$PAw1lJ*j-a$G74@xHBJC>XojdE#4Sk?#<(kn?_^!*LN+miS+8dsmN74k!!{FNQgmq+N3+N1keK?W5^M z2Wm8Ih#+QMaNV;a$+Qna%`(6S6UFSC5^%L;z&vR4O@m0hs%53{{m3i$AAQpqPpkN>-YHai zhFu}ThPyt}@3yaDB=9fMsSg0wnohP;r;LH8I+*6~eGjLde}I zXT62xX55yqJrAhDF{S%FT7y5Mf%>#tC@4i$Y8@WDq!^Q zY+m-+9$56hH2vBEoa251#9bj<{!M|?vm@&R~Op0B21!Rw`&|J&xi zWuZP1z4%gEJHE}H{8jjL85KM|HmFSc!nXDFgwB(lZ!&N)%4Jxmw1gUEFf1 z$rULy$xFFmc5v%MwPEiR3Y_u%+*-e&)axGgtOxGzKR%rHgv2qeQu|g)u!UrOpCU_C zwb{q0P?b%`nT;8AdROh>*4WUp{Rvxa$COS7UFM>NzFMnA0DgMOJVCyiYTgps+}l0w z?%IrPF{tj9n%lnDcZSwc?`8J`6CefFT>Y@i1Ggx{;!LHcRpHeb>dMX{U`Xp``k4YL%gjx)Io#HHI#Pw>%V%; zf1XAh%KyyXbpd=uNBg1K=KuPMKRZ~95&XQYUws%&8S&rU?U#S03nr!+E~WxMPLkYX zg*a?X@XsFk%PlV#u7{}N^l!+p-wf?kzejIznzkfog8I$##$BL#>vG=b|$A_qFTCe~}8KuDe`PS}y{%qbK&8 zDrSVz_~2_^q`yWsU;Mt~^=~VP_?_+=8RMt_8H;`?IRWIO6Eq9VZ=WriC7PwmRfF;S z4|Pk(<@dkapj&<)s`vYC1B;^zVO9@dUcb^t61QuxsldoWP5Ia#f~{kF-Y{C$qe1|SFheji!1Xaf(p!Y^S?Yql_? z|GVEOhV!k};h>8Ddb80*a6}jU+kmKl>sy-+qssA8QXA?I8hXF9!^>}p?MRa|`i5gjaM=K0j@&$M@pf?pBhgO5ebR74oDeJ) z<#xJzG~N+8BZ&tIb-Tsr;37Dq?0ZUR0pmLrbyfUN&hdi18o+H0s<=BF#glEf6mLcK zHMf-KnSyl8X6eFtvudfZsP_ES?UElG1-la9T8?oBRf_ADs)r)w%egXl+}fvwA2AnF z$(xnt&?&~3Ofq~U*$>Wevl_aiiY6jDc9Jw2O zaUZL-6^~ikpGnGCm!_MNt}xU#Yh&$MGfGM&?mxUS4D_ab_Fyin<(}r-FJLYLM3_AZ zUUUM=)!z??a79|ro?IQ1eK^+NYN*hL!)F0PM&`**eVcYqJ}Gh0QZJvk^N11yq3X!! zOm#UaKg;ul$2u=3fy_RgODwuf!hV-_s+-DNYj``@klF>jjAs~aUsXOhrbnLf%-^}X zW*DhDlGQ=zU_)OFk(1J7H+u86Q!$wl^^4*PpNiYP2^zYUN^93tFSXLQE-?cQY$`U{ z6HdSVek4(M zHvL|fjpwEpfnfKrE{mFR&zSv}uVhr1NiRob+A^SImMW#5Qtm*84v8LMV}Ac3LdRy? zxs6c{u8iGJOkbIvni3+^;$gi#ifnr1Z8I05!N1Bu5}a6&RRvRp=0h*UAd$fEFE;e^ zmI{5==Fp<#22}!uDY?(ruVS4bR!K+G#8y^TPS&2+9!hK8$5Te~b#2?3-4q-Nol98@ zz*iR}at-ro@+mMpJ$clK29Cl*s9QL>E{QvHMm`+s_+JUDmcb3_d1RY>}QhlZo4CSl*rFq0a6HkOE;;n zaJxX68LK6Tz4d!?IN+1vE4#_ms0LCdZ}Y67KPae8-u)cZ*)mx&se2}|E(8*X4c!DD zt&tEn*IwAX24+8eq9zpNJ2=YQS)qmBf}@lY!augof9lMm-rqb&qUd6__HCDbqMi2M zqjp?+VwhS-Co&ASFnaO>7S{XmUMO?wfk7#ToACEv1I|T~1$=gQjk)FV@w?=-7N3NU z`zXDGEAlJMHaKm-5N-!IF_HUvs#v4wD@^?OL7rCo zl23{o3Tlo<9C036y~22wD|pyNUnb|ihP-(HDcX%U1cd*14=apR#8zk;_EjXAj30y! z79+^-L4x9J(I<2KQqd#9J1H+xj}8YTW;Ag{`$c=V1d|)BcK@vr6v~^IqZ~Jb8pBcw zV35wuBr7*Wmj@&a(&Tx9;v^iz+lREU;G-a2drG>;Wo(v69A}XFr)(;?(?r8?1aifY z3#jL~gFCd_FlAq-(Qw%954{Vi9?kWpHhA%3_ds~Ca%4s@=4phfib8df4cAL8U$7=2 z<<#OBpkSlJz1Cz;7L6c~*d5X7Z@UmCE$WYImzsVQyHKDQ%yPTysU{{&NFyc3?(hV; z9gX$#8Lp`(&^;5k&E^_v_&uZySDoZpSFg&8X?ZzJSz+^N^8W0#n>O<@FBLnJn~LnS zxQ73@5%9`!0Jex;H(@ z&ppq|%8$vu`Of&xsWWXq2iH{iERtbltM8gkiJo^S`Ey&UEZ{>5FQ@zK6|Qvx^_DZ*My!r# z>rN+l%thdQk)w$U_WR~*&xqmg@sDuquCrH;SXtFe7$#kglIJ(6_&6BPD znc2hUUP&-a%QcJ$<(q!wcyfU^g2)GO8yZ#JIkZT)dRfN>QBDy?-^AVo={_lGR9tn$1WZskJ`&zoMd6&9BwYZ*v!n#x9P@6-S0B%B zf=JHE&&lAksP-ou8NR0Umpo@o9zlFf$NNHm;Rs5_ zlxhN|4gotFoYJ^&0%j#QTllJ$g?i6Z}M*fP@96&lqmFew{kaXBBT0*?Q|%=wq1RX{*q>;InR+%ioAu#z4pNn^;q$VLH#1Nol;mJgTUwV$L1kLX_)A^++(%@A5{vW2FdX3VMf^7`Hc0- zUD)!2j7bL$*1#X?_&rhWp=tO1Hr^z>>i{`z5$#f;Ypk9=A&_D@_cdR-H_;>Vz8Ln) zqRz2nHc2YT{85#Pzxnbq6ge+Cck~fsrfPw;8Acht)4>{OfYa_pWo!w!%bBr z7&V`Ug>W^NGA1wIZ%yTjgvp->caJ5>-D*4K;gexE_%-^H$^E1pQg#DeKh)FE7Dp>i7TihgaB>@)T@WJWa+*Zzoa^;e6!5P-*dl$e6vPKIf zT_QbQN)@z22Y#L6G7Cw^jc4usFfR$aBFAU>PHSmKfcDPGZ`6A4lO;4FiK+0gNQV2_ zrsJ#r;E>d??F|hA!QC+p$(3P`h9@36z;3s68))!Wf#^As!-8n!F0mbu8`heT=SWrFSse(*{*IZh}tr54UTfK_rhp1H3PP zMLjct%j6UPA?u7#bg_#NJSiGI|C=;7wLFrcQu`V)B&RQY{ zYki1rnjqc#9HMHam3Vs9hUuB~^717@z8(?0wi^O0-OZ^6p0;(VLv3ozyqTd`hG+twD;t%l| zW|BN2&Y;Zkw)sBZ%HM13z?C4(0D!d{ebTM3!jdxbKFcYajjszd5;0@akP`)+3T=rV z3zjGw9Np8%;D7~K2}X5E`0&7lt_7XjRo+<(d?2e@3l?=x1a6fX}4|y zcgJa_Wp?Y+p{=|ItcvbAGs8mAf_W3)kwGG@1@0a(;Yu>i(2ej(7gh$Gs zOU#)EoDb(|d=q#6x4Tu&_+t~QF7mW;O+pVV%QuI2+vDC}0-MgKVnkeJ-l{KgB+j&3 zYSLhFvO{J}%@hst^zI;#hM~wUfzAthr)y4lSlhg)RJNZ@7rWG0YahAsKIoH$;VMF8yQG4J?%0``fO{zpqKo)yQnBZhHVD{x}DXV#LIi?dfI z@)0HNHgDU!pW}}({5=CX;93&Za$&JTPcA?_nWuB10n&Vt# z%^F7>EzVQA#e#-##xQL(yXs1(&BA2@gUEIUlk3X`fXVj7P%$8hK)>i9s|b6dhK27K z$8+arqUjJhoX6lh%xANBvubC+wX#ES6Xrj+DQw*A+k*y_IPV3 zh)AHD&R3=zC`0WldlJ*&pSgP}Mir9+*lE%{1)0(=Cc0f76&Ip8GFqsEE zU2lc2wg(LXCUvNIkGgcc&veW8oDub^HK>uZZci1y%|urGOxg&NhOgNI9<%C?fyJaw zo^{k~%-rV9^_B1T61KPp0+D{t-IcT?}_TCyHo-Zulq^f{F1N{n}cLRR%HN~n`OhuN3GfYm^ zHj_YAJ~oL!xdGB#W4*qp?A$@C5p+<;z1I!W$tBb`HE?;Jdubrr4@TBrqu;>qgJh(zPFnf2sN2pP8C44!SKe7M4< zM_E2&SQTbZ=UMhLHL4J>&P-^Id<7YYq%m$cjZ*e;Y;TM$w|_FA4#-_pY|}6^e%iV@ zfA>pRoYtJm|mThgyVhqiv01u;A-x5d)*}OkW8LvBxg{9 zCF%)p+m{30+PT%VQN^mDcRZ5AoSFGNHw363Xn@aBZpBwa7@>PTgKOXV-FCY!BTMrN zs)|yq@-;aFkO?liS6FZ7WSt$wT~=y+wDCTcvkhKi#JSwiKh&G7bfMLOoar<=VWC$f z?^%JJ|A(&k3~TD!+I|HDM7k&-Ep(JBh;#^uG(n=GbO=>IdXt(&1O%ja>0Lk+kluUm zMF_q3PJlo{65jmxv-fk(x!!#~uj^V#R^}XQjxm1sJ%m#KU|(dEFTpMQz?}uGl?T-O zkkaQif1`^4IoBUpl#-V&7?M3M1x-(vO+MWX#wa6c*UGH>D~@BJE6WO7BI3>?i=_%? z0W^Pe)FkW1DDV>n7#s>qpDG`2j8u0uri}G>5D#weStlN)LF)t!UDG`;g?}uMvx%E! zd6R0?K#e6JI3kqfug1tN%wAd5r^U*1w@YL6>9JMFP@58waYIp{!gW_j)Ij2*4{5uD zjQ8O)CxW-|FlEfy9kzkxWL5Q64*VOA8G)<@@~46RmAH?eSI&VhMrFRv!C&Ki_HA@= z9JtW!NtpV-()OQFn^UsI`>n1+-%!gylDH{qhBN|SYIgw>Npd0+H|Wtfxhgxz~;#GW$U&{Ti(~^#qMq*5&i>B zOSNNF5b)x@fWY3>+qYDapb5-)^ngG#BLjooq0at-e(Pyo$IKro^P!`^-Hj{k@xoe) z6w4ScZdj-@zH$J|!g4~EuecImCH__|?Uus`$ay8P>||>?nnUvIcs(d|bgO2$X;a|6 z-bA8WbOBOh`N#XFlH1zb@kbqJz3^7=+!VMfUFden6>mUO`cPOM83}df>Ip&Wl_jJI zK|BX-W1m|su0FpFJEZYi`8HKu<-4ej;1)=gl8OEceBg^X;yLg8t5d()5P&nbE2MsK zlwXF(W)Mz?TD=~B0enV2I?TXkq6u4Ef5J58dqsrYz73 zR<)S9eh&hn{1RF3dHKQ9m)q|>R)}ls>zv*MkRlj=sdqn6W&cT6ZpfQSr)5ghei-kv z*JhOoRG;L8{oL3ekc#Q0+*`P9a6m+7t>;ul|A^3?MwYg@ zJ`KYc?i|2CJY8Bk@p!$5X3>+m)%jVMr!5F6M|(=K3&se5er*yHrk_10mn^c0Xc@I` zULOmndV2VxaLEOb!O7WhJ+*bEmcSBLIrH6{DGgCT0FWy^baaJr>?iFTLp2cpd`p~T^SfCn?yQs6kKR` zVOdt;*zb4o+VX)2v%`xtniRfc_FI4SL8}Okr?{k$O8Xg64OlIIhy}> zYS~}W{E&u}UWM?gV^M|)1&6cBGR1jPKI=QD%kM6f5e~R;3f1yu5>?~JP*wQigO8!O+>cGY_fj51IzFz?qn3cA(odEcTUX?|C#pus& zNHHt*;+Cg6tPh}MJa_s&RmvO&EjptCo^+J?X;5=E(s2vzyrZWn0NjZL`QuKNt4(X; z1H2#g7B@z;(RupUc4}uwpVi&GV7s(@D-2->v6Ox?`dKp##_pJf;b{4dr2dTr?EsBd zxqiJ+%K)MuvqB~x&26i-M)%Ch(UYCbav}%CLhYwv8>>|1XJYoUvuoVg$L;JWdgk-ExUP^8p^RsAL-C7uB8L(Dzq z^TJhK_7JndiH?>R$wp#zC)(t*a!3I_{jZK*LyIHg7{7C~nzrvW;B1+EmnO^gjcQN(ytvSj`$|K>(`H&4y-cSx$csm*=dO$HsI%>QCdasJRl41@Or+> zaQFBncPL4gb<~k~GKlZH_r&8@1Tx7qVD8JP@oj`Gd|UcM*@>hG)drlyxVJ0i*)|tp zAH>n^yia1!djQ|G4$I83*^SbPE;|frdqIs(TD)(_q)~Y)vg9Lwwr07DQUU+y%mdrc zY?eQ)I`Q5JiKatXJrMiAowqrPa2^sloU?)L++h9R(HWZ-k}qX4z8acVK>r-n?k_;K z>XS`@B7gV#;&UIWU7JR1>Y${{n5qAnvt5zcaX_r!mvI_StD6_c8;q{&WR1mj-JC3& zEg;}T>B*DO)gB~MA)Gzx@~!StV&!ehSw4jlvRb#*x|F+xtZG2Wm5LXz z3s`z6>^+&)gpyRzTClBJ&WCm)4hJ4v*E$>Cyr7QT?WsDxTKZOoy*oSFQ=LWjsJL(# zpW)opAxjqj!bkr)+>=zs2vn8TLK5=j(Ct@T;{dy9Ip!d8tuGcwqwi};0 zo4w{(J;th}4$z+KJ6O2FixN~C@s8uz7EkZz2@;DpFXn20ooe&}q&bC9@5?zU7yM`( zz1M)Y?^V#OyOp6mQ4f_s9ws|%8`-6tQMmB44?b5=H(Wq-wF0Ws-{=^?G+!9GpOv2u z*~~-U5-vbE7x^8&OGW%q`}mFOboL&Se70ghk4{`h1KAk~wS%Ww_{S!dE)Mdp9Au>B z{7Aa0C$d@D`BaILi*?>>JZqJURRstFYBuEGhp|6D4`s~#Ry}coX^Q)_7Cy|t) zJw0zGwU+GVdUXYYj2e$d#Xq;IWzScMFVbZ7oHLb$SH&fH$iE8Yg=F@q06~FY!Pevj zWp3g>!tGyiA>%7no{*pU)p?e(?By2-$^BNws_-V6Gdsf5s%CvD^>38pe-0>8iCWhD5h<4ACWYPkt@EJ|NHF47{Fbs;a7;OkA00kIh@6Pb8<&NZFV7Mu98CZLX#~P*NlmdPb6<9?1 z?0pebytk;8Zo*l;<@I$^Ar5uU@C{Ir6=@ILpA`o`t1ZjHC|^`cN26sqa2#0Di}(t~ z?0dFZc>9=Mp*OA9hA_!S=r5S_Zy5CqSRFq!m-Q#`4a%X*6|Fi zOnx^5w5ZV=_3*GIIp}nfgAW^lYq`F|Bzb;V8r`LPT)+w%Kk_@+MSrpK%|QxnL{}vc zL=hopQZjwk`d=tuxWD%v#P=#_0k2nK4ZjMS6T4p~s_gr&V~8M4ApEJz?0TIR{mE@%WW_jWn}98nUv2`KpJ$z zya%4JoL$ttlP$dVerZmdP~)Ie9CG?rq5Gw4%JL{$JY!2?sCWNeaIfvwc3HN8X6cWk zJeA^oIQEOS9NO>PTcnKMcgZ!bs=Lqpm;8RzI`yrQJf5bEmnKqJPAqy0b&fLdp70lj ziI@|c_##+juxp3nWXH7e=xX~9wuIo7v+5=h7YeIm@%zG_~-0_6G{uZx!WZC#zb-1pdz9&gv!ru^Nag# zoqdu{@eW+%xa!l0o`U4*_Vaz@vjnnDwNqt(@%juWGtCj%J1 zxa!};2pw5@%j&$Pv(0m!&mw_0 zpK63m38=1gqR_|d_{_X-eG(LKcsA!eRNzzD+IvWCbQBLP)UgpPu#f7LP?@Qa`=cmh zQ2K$HB?%VZF`N9AvIgjAp|xI!{eoI8wh1uZZ~4Z15~8x&fj0Ns@;bTeK`mq=k*FZ$ zbnCF08n%jx#ym~UB=|YN0LW3pwLO4c5^gHV4nG@dHe9a+QSHPjnlh$)V$JSl=;C>K_D?|2IyomF;mJZ4ri$zhm; z4L(mUgkJA~-ipmMH#9+rGRMj`;pDn!NHdJM^js&yji2ameJ_gYGXYW{N*$RM&ek)Qhk=zyhNdy7=ym+`9K%%l@{< zGRdsGpyg|!$5FFq)Cs+OK#OY>dS9&wX4?$=5+rm|{E+zO+r1k!@z$GG))OD}yHn@C zo$DJkSwfbrEeP|400s4}fU6{U4~1U`Rx@|vR!Ds$C%dP3M$S`_{OMNM4Zp$>M?8dW z=y}+|pz@n{qlRDWR6B#IYgd)^83oTQ0%fNOtuxJBWWWCyNmH2@`F$Ys(73H8^#bI88dx?_Geo`V1o8j0C+3RA$rj1UddG=c&=zY&y}F z8@Mem09Q3xVG;|$K*V-*Rs^rjJ2YcIgS%vj1 z;K!l@jtRW!Q04p=)(Xux4i!11mC$#k&)Lv1C5ta32r8gG2ju#@&F&gfuUl^a?vFqV@W8#tO-x^V1f8=%v!1)R%Ux>X(1Q%gnPRmwTUu8x} zuG!zDR0X@TyrRAwAB8&g5Q(4emQ5r8eyxm?19PH#{wQGaPNv3<^{ySGg~_%<24{BY zw)vg5Gw9L5oe7KW&4cd3F~n2t?0T4YY>4gL_H%8)vpmS@D9|6F{0V&h_v6jyb5+rjsqV$A#Xg2L5qUK4u+f4vW^?6|$i&i8U?mcpvz zcyJ9aYr#Oqw5%@g{%b(LJa$;JucXgfrR5pccVPP-C8}^sP*NGm@Dnf=SRyHx*1bZ# z*{;ip9ec+HQ;&NORL0Q4ZI)w(3ju$Xx_bR?#)RhXZIHorXH>PDU^(t76lH}09%~7% z?@Vh`8Scc~p-;|VALa-AJ+d0>tip;m9+X1QH{bPwNBFT^JI=CmqWxTP21lM8$J#FYecb~ZY`*2BB@8w~~)*OV&y@tRf4j&N@wmtCk)8W?R+qoZtZRc^vp@#P;q-SUFtfA}b`Gwsh z84HRi&F>D=N1B~mbe2~S&vy4LNfA=}%e!%>+lUepF;O)Imz?v(Tald~l|$Prp0Y^# zwDGIts<=I#zELXp()FFOpIiE4(d+))Z-twQUkxV{G?da)jk)9(r-^}yt+s~F8gIK{Y14jM4xetMT1R|a(gzMqL3VRhfIkkM6~6sg zx)6l3nNpOj)W%uohG%hKpzKWsE28TTEHvLH_~QE4p3O;O{L!xb>nTxUaI~Zce>+<}32W zK9$=mcyu3EO-RGLwJg0hTJ4#i!voD*t%j;$L1XV9$-e3JM~2CrP?L*R)U_Xq^r|tF z7?FSgHy?abkAo1-QMAkbdCTSg9qRk3`46i--=u}GOwMKL$33ES;*2|N{E_kiSQFrJ z-9HKK5zEigV2$2;r+m#c0G`P?i1buHcr4$GZsfq3V{P`;Hp}q*Ml#|&%jgI6vKjXg zCGq~+pzLAu-Lv5VU^HZU_eVSK`9)xVbI!Hb6v*Tu9a3+FRG|gOLEZWqODkm2F+}Zt zCDIXS&lchmlVLg4Lm0Z5{yT2Sck^IeZkksukRWq-~5=OSz?4#+8UC*o0jL@qEnR!enF})qUZjT<7SKT zly#Wg+O`T9bQiJuB=ze+4m^~)5FUvyw8ZoY>RU|g9X-R0doUdu1)|R5r=8eMry;MN z`us)w$@Vvr(Yq=!Hb5TN90JdT62z2p7Z8^D;U1Y zvmSDwvzJu0T<*iVqR3lq4!DU)&%p$q>Y$0cH1;Gl@yX>6AHIu%%X7wQ@TVJ^3V``C z-iChmK6$Fc&p2zc&EtXZd60MX2EBr~JIDVjup{31ybnpsgVw%<;lU)t$wlPyb4;Rh z5_SqhL}xTix@xZ3e;xxe;Qi==Op`R?HZuNPjPp<;cobY$-HNN z8oc)pWq!mOV_*4GiV%D*C?Ze$M0uAQ$}aszDCjJlwcgAAI%DtBvS zFU#p`Sfkt5p6uGR;CSSFrXgNBLif}uKP1gD+N}pEab+f~Dmc*1`&jYZ5&FQgWU_(D zBYNZGr9Gi!c`KO@B3{MM7sX$azjCub@cA*DjZr)oQ+B8ADtma6-dVf+a3Q@&Lm~O? z{`5^Y3P2(OXlGCMxmV{IsZal;{K@LwZQaLbr>ES5)}_S_^jZT&AT`9UWQo7x4$d_t zF-7bzg|a`bwm~DGsgsc$A4`2U&O~j;*(2ubBv(lH8_zPItEf}LfG3mXj~I-L{5fD6 zJlD>BxG(-vcCR{TrS963TPxj?DeytCro6z@s@=?yQ0W`8D&(L zFpqZZ!xfo#Ezk3NIu%BcyR&~2sVE?1K4Y|dSV%BRMsJ5P1=q_yWeH~s&dr% zcAq1|FjewO^PCM)_?LK6G)%0l>g+Yn&Nc25vGcFx^VR?P1;`O-0>j~f%5k?3)meL4 zo*rV-jW3q)f}A{ z@#l)sQ<(!BjNy5;l(N$^^%g7+>{~?-CJ0k+PKy16WDEAANYUCGSssyyPLc3``Y%pJ z{=d&b>VT2Mg##|Y;*T<4Ro?-DE*tlG5vzbf{;WL)w>9Gpd~aR-?ko&~)%+xQW|ye= zd+ym0x9v0Y@c*hJXuIR^kdV1G{ua641-YarKzKpS!btf42MXT@WkUlLH#HozCX7D) zS7IIy|2N#ONG7a+EH#M#vih(0sj(p3ZM<`W4#30z-5<!)VZx3qo4HqOQ>omfYIt=o17tt#-eKkulx&L(_E`ral+YYYz7RK{yV)prmt8GWXxGJB))ONzB2ZN0K zt!S&iZuO!M6+Fz9bIuJl6QpBGtGe=0x;x2Ly2mbxs+xC^% zkE*3Fah=*fWE*965S>e!jVuy@zB0$zkFTA#H@)Ym6Gl3^+fR!ebRp-;EpczM-cd)?G345AYD zNtpVWS>0)+UHF7br88s##6+r^TyJU72vkv&9g` z&7i)8Xb=~#y>(o$yi>e;%`(TeE~^q-pCI-j=%@UJUMK&yDs~4Fv@Z+mO1g@~g4l35 zNZBC~qnj837C_EBuW_GTELHW*@4l>%X!V2;y9{_2&ytsgO z)qQy?L&rWADJ9ZXVkn$t}I#$MK5?*;^TF+^+%5IB;%GH{z+EG#7IXBhEA0Fbq-Fp-)_Tz_WUn^WO*bbB(v zo^9aXvkPb<1NetbRkrmPa92@tTa>cHu&LLpyph1g7U!T_eybn!Fznj zjS*=d8!xA?;nzcyu=YE5@WeAt6&rLg$+LlaYPW?w??%>k)72r|#rOZN+a3W-J!*(D z%8Sd{IYsuOD(Uo0k%63CA=fV7r8K3Lz%$lU$B#a4aab+)4fpTt*?tg9VN6)A+1cv;>;xU~&AG z`lD;yq2J*LG?~?T;nc7d2{Y3;G5hlS?f_0(erWY%L=KemPwMlCb)S=K5#`B?ISRiwSC`$Nx~XWYazvR&qgp<$vf^1X0Xd{)XLNCCA!C zitwvAjml#|>QKE}`9n|<&V6vB8jy9En)Pq-WB@$yb5zs9t;=eBm;pt~eSsniVPk{T zH$e)|wsgcvI*2Sh6K0ki3jgEMlrnt|) zTLzsnUJ@C6>Ka~O7$L~ae*a{}OPjY=>~q<@Plg_)J2|BF4woj7`_yxr{Sp{Ifxz7o z^5pUJPTq-X%*)=pOHK>35Lwonc1KqDM}wO1(eHKfnh(jzWnuolRT+}NW%cqO$^m*D z1Hlm6g(4H%sXZsdY3x6!bGx^08-+kOhX<~y;BRwNNP{=C2Z(xb_KY2ZjX!qZMO_6C zNTMTDgnw7|I%x(**QZV%=Vl&hH*CvhImSIZBcZqR&L@RD7>J%AG8ug$%ZB0kn`{jp zBmREmyQ8&tVB8^5o<3On6PXJ&EbEQsGUj3OFR?GWv96YW}@4r!-T0Z+8v;QKnV%5RbZ(Yu?TI7jYgcQ}xBV zb#aQCA$jtA>Nu6rfKM=av_-VPqzdwRbG?ChHdp2=9Dt~c!3GTgF%c+_CT zrK#Z#7FIhCN%W2gMLy>)@2QpSG?a}NZhJ9zhZoFmtsnn6);;8V*P@FGIe6LyoPmDR(g6+X;@+?Gz1W z>KdQ5d(x~$JJm)Mp>f{3pvv7dzR?_9R%1-7ZcfFMciRt0}4lbfp? zkM+c?unzm81IzY5N^GvF_YCP@Hk4**S$>Vqci$>=r6Z7Q|I2sG-y2k1I2Kq4O;>#q zc(&F0QYR|yk%rkCIjhS=)YHKzKF=cpZeRh$*>IAP^@3~bC#FB!H+ejlIFa85)k!2# z54)W!G!4p$HoiZU9P7=*w46NjAzZAl(TWz93u!?*kJfYhW1esBZ|Nz0jh6e|(t7^D z*P%VR9%H$a$E7yjzP+ql$OV3!uJGq+5Z|+@4w{dEW^zxF{V>MsLvmThPn&R+|G?>W z5uJ8jp+r0S(D$kbDs^X7>3s5l?BX-$?7(U4XVplV^exOPkGJndU^j6V1U_}B=I1~LtC|yOlan#+jcXqTRW7l3XLq)lKJ;+Oj-RZg;n18s%y6NG) zaSj~UkTlj_zdns3oYBpOHb#iy@Z1@MYp!TgBu{x7GCGUwi_J?p4oy9@;x=kzt;l+f zg8wpo<>+^$5~%3_u%7LIv*O68?`_cR`${P5S9}a42tnN+eO%yi;V}uRY*m!4#)2CV z0Zv|vvlkGV@~oZ3UP*l*5pvg|$uJkfXQKABSK&o(B%3m6mQ={6s|9h>+w)82r&G>d zK;JDfnc(@+@gms;D63ERTRpEToYkLhtP6?=87U#p-YNpSs&D9WbOV;!%=BMG1BHHM zdLHMT9?pouQz5kxt}liW+mg!vT0R-cAXWk ziEIC#^Nw8Sda@)ZV2>oxzo6oLs`)RnNxxSD0K7_KDa++Y5~*h;trK&eqZ5OR?z_FM z@qEU3u&qemv(dSS)??QMA?$qlOZt50XgoeUD2CXfvnQU5O{zp7nJR~Ca{_b}G_FeN z*BJh~=t$Y`&?Gyvfc2C-ZLXVs9N^PG6Q#2%e($cC=_YTIxQRoeLy6;0kx~+4IWN>c@LYHxKXp5zqMz zs0A2AYKwJmoeAPoyPNK37~lK6aR*;vr@Rfj2dhq(Bc_3S^JQBso7(@xZ2JZSM5lyW zB$G-=Iz|U=A6(m98|_9eou`(etSJ$?@Q>%z^@ff$hxtZ_91aA%3&rj+3Oeyj%IU#=E5{t?~;Md>AXkX1y&C$SWvXFNzGYr^M zy}i($Q+8&XzK42+$b3IXcJ}Ubdl~ zU=7@J{$DE(qomIx?y2tGT(zn0fJ-Hj0V>kk*KKrs5jh@gmxjIXF;rKM)l!js8E|MM zRQ{s`t6@_-ZnGg|;kF*{zRKl3+1ZB-#bZx8@st<8VE2yW2kSQ?ltSygp~g*WdZU{;~RJrRUsY!@+(c*mX~yV2czNi)PHVA zPs~ldm$6=8%x!d9FC0;}J=j^?>CfadzIxL78ufzDdyOj4?V3BC?MWCp=+PAY0yicZ z^NKJ^c5NuQ%4+nyNWc{&d&!iZo#2w^>= zJMp7AInF_iz*jI~Xb{O|AQg&Pz#d#O%D>5P1<1kdUP4jhW>QAsOn3j-bQRZP1@Wt` zc=zwTdqs8}Xfv$3Ns(RR9sjc0Kjb#?oNG}lWZ{oo%qJ1KS0w>MO#_3rf z-m~rU^4$nT2x_3?V-n^<`WcwR+0Ldw!K^h#ra5|F5O;Dm>$Q(u7Xh44GQnCQTU*ox zesj(MU>2^OL0AA9Dl+#1^HE**TBMi@qOc!`F;{@L6g^hBeuFx&{detf<$|vb!ZiAL zkDkmUySPJTpV-NGTSo7qPfpkNZwh!@s24s)?Kk>k_Gy+2`Q0n_`%tEnq44N}Z3ajR zRWy{60Pl5l;_Xp-0lMB`ppDBYwJP$ei?&JO4+;|?(Y6O{2H1<^B6_l4-D^47!QDbx6|*-`uBzYUnKU{>*EcWsOF4$w~*LM(d9+xyKbcG z(9fqQIt&%gL7ntRrTfu8V&P};cD}&Yic`ym8zVef-84M)^p(Kj_l5BQ0$3jYxEhV( zGydm2V0819Utv~bL~7si_|xP6Phz#F%^97apj8G~G!JMjXmf1~D%4y~~* zqb>Cs7I3fYu))MqKz`{V#mK6}SA0e$@E5vW4UTOxxIwMioKZ%KoG5AhxlE-ke`F@) zXz>UyblxhyQMlHKV&)gjHCC3WCgre!MgH46$^lBaaC5Rvg13(8*QV&`&kp449go-a zXlvBQ@ZyP}ZCiA=*91nhG`m+dv$KrrU}NB|5akBhtFYx@@OtSV2%^Gn{YlN{h^(~F zxVEeef}NgKilt?VN_ye;mr=g|Wn6c9$t-=w#=pgTd~5caT#NFWbk(6+oqy|)&l8ws zXcn2^o6t>u`W$6F#!E+-Vr#vsSOKsVfU6(!$U|#F7!j~RONTXjyHe~2{XU)}eQNs3Wzl((9*`E621u&L!7t1&LfJ9?vF)KX{yQOa`jLYXw#@)X;3m{Gk-DFbbGR zs1}_`oD)HO8D2q;wx+6Xi+Cq$H?8tsj%!^7p9elsxDIL^UFBnI_1{dkrP&_sG+=_D z@Ba@4y$(#LY&z-X`HR}P$q`9Ma@gFc?|X157EY8e9qwLC@?i@uq%kY9(~DSF=<43h7B;X+@kzW>qb-ZM>=jX&8rX>=ftdnEE6BPLyv>`ecq2V??3;(x_Ycg zv2)__U^Fre7DWYcPis4Eq|I6^G0~g6I&ml?%a58R4{B0-#>Het1R{ z>(ejOh<)M`pT>skUAd9V?i*7rfoWFdL(D*XB&;rEOZbY=q}n4ToOG6bT7Y%=2ayOS zxN&=(fj^5m{mgI71a9)uD?0O)yR5=GJQ3N zsAtzS#BOd|;GyKRsbj+AoBx2q$lhn${aKU6_+rDeUUO#Pf#}eT9xev@W?30gNkedH zp4#I5IHW!PKLOEu;xHOdPa>yGszD==x|~VqQm1pEIx| zfg3sFjK*jGjqPbGx5pJfHlr^)X<0VEbk-+T4-q-^aa&lrz8J`NSOu>+yOB>ivDfjd1s$rJ8+&^d_U#9%aOObcSy?=-G9iIx1> zQ1pe6_2r#oifF#~v}zd#kGxnQ$oFdibdNTTf2hvk?fO-QN9RQG76mb{TAr?AP%OR^ z%a6R6w8lXJC!U{LA-*p|v@*N`4(TRK>oR%v6K1Ffz7fm#+rDvZe(6{Ks8T})ma)|NqH6euG^ zKd8f7MHFfuDYdj9!M*rIJF5Ul5$Jyb*hE%GM>-}PJc*;8l|U;|Z~do%Y(?w6+nu~S zr3W5HjCjIYPr)0an%W^F50qIj2OK1KqR@C-OT6Raqu82ZVF3dtvqh^UJK=Gcz$I7^??#6K=8A+eJ=puiHNt?+t)g z3ws%xlt1)+3%EbB+Q!!fC#JxJZRH5VSgSZ-7(eElfet@*<;YjTOVZxXbJb`H*wtYo zJ3dKaXF|AcZk%xn=neF#Suwj|+&{-WbT^kA3tYgYZM#Tu;tMSt;2Bd|OkS`ML$7G^ zkf?Mo6`kN6Gn>zS!72hj?)?Q(T_-raz0sYQnzRkLRTHH`xjm!0lFz0n%P1HKSpMa- ze$+_tvqa#;y~DlvmUCAkF=_sI1%7bm#$(mih&6{$7<7!RpXKad)yinOId3J>3v%>i zw8V3INok5Zv=z~>aU7@u`7hcM6*8bc8( z(*a0YPwETeDiFJ zRFv0~CHQ3XKSi=g?*OMbO`UA`r&=i1GFyQb()LTd|+(X=ZLc8H6Cv zO71~#P@nwadH~T{eOQ`YK#&y8uYr_RGOBb63zSbx1Y$w4@vXrq^;av-4U#v44|s{a zvDvx1Dnu3@2z4SNW7A2sIRBo@1$I%UKaGvMu-8rM_rv<#vcd3nqA33m?8I5G?_D;F zemcRC**}D`O@`x|JegPv+V)o)(n3W&l>RZ)gZeAolz?TA;`PQDYS{_V{@c%E6Qk4l zN^g)Q;k!-E+mln343m$CwEy;lQNRoRC4-X+fx={Pw!e4Wy8fx+z0x+2UvZ6W$07n3 zz*HjmNIY2Yjdw#Qomb1Jryukgk|U38W*IVF^kVr2-)x8l3Z0LBsdKkFI;szonZHkO z$4F>8{P|b+XFQxshEUMkNel}&x?_H57Bw7_I^OPK@a|KQj`7)o1zl#J>#Vd3L0N2D zo76q}akKSu<{7>A$2xUf!7X*!5ck|URd$dd<4V72rsIxw=4b4E?>}97P_1$SOg;;F zNW<$d9!mLbfItV9phh`5*mJxHK6;Dfs&cQk5~izEeak>UrHE7;%=zLU4LAL4CghOR zXZZV~C{gd4}dmjy*kQL%b?W`h%+eRS4>ww%8m^z6OF1^^r32vIP*W;{@K zdq&NJLgP)DG%LLULxD0T_S~I+WrB2 z5|<~W@?BvNU<*6@i9;ht;^gqV1W`lGt)C=PCO=Zk=?Yx(v6*TPe zH2JGCK5RQ}=GvTed)_qPuD{0(ii1c~9EF~riZca!;cb-hmc+#Kq2Xw;51VEFFH9r3)cS%5g4kkxATr;JdIZ}610Q{GM%VM8#qe+LTAw4R?N^F^oBzww2-XnBiU;c2q=tD9%*{3#%yTsSyz<`_FZlVjAndQRb#*I8OSxYb zw#dKVl0Mvu@qc5*n0j_KoYE?yd^BMDHrtK()?TTn1#di!N7S5>#gnKgA-3$n&-X4@ z)`$sH&KuG&q#1m;_ME4Qg(Ne)3v%pJRXpHf%?=JqudK>WTB=KNm@yY5nS}bA%fb~g zVTMDIgKt99bJSoynQPl!(4-2l{Lclc>Hb4TG^(LX4wjJNYw=E0Rk z0|cXtp~h4`GSv9C>$9zG+C?9YrPD8A6%*bH*ArfEp9x0Y1Jt5R{!z>_DSpOv0l<=$FmyiZ9ewMg2L4RJZTe{ZogBZ&Yr0X|XWO7OY>rsL6N5Q~2q<5r>0-+<)RC@2yq&MkZ z2)zVILK5DbbAO(D?|+>8>HWwU3-%6Kd#$Ojj7U1|3R{qJt|5seVNfkWpMWaS}sk1$y^Gf?+J52(2p`qe4jKGb5AG1QC!MWR{i>W$xc^;)L@b81nt7zVu(Y$S~GQ9D% z`%m)We*tie!Ow_;f3w}FRXh4I8mn&cZ-m{7Cm)`$t8BWZb?sj-Q%Ms1`(E&GsmchG zw0|{a_z%qSN8$bVwc0;L;62{|e=c4iK+|3Km;P7( zR(`L24Qpz}@x`6p%2Iu$lxk7IyF0#x&$$Yg_a3sA%1wRoNqa!UdF%d+p_}FG?dIqA zkC&0`C*dZ27JBty)Ot#oe84_HFC&zDjsDhI&`IC&Etm?5`P2Y9#Z_^e7_D0ju|sR0 zC`gA64Jb}bgIN;#tH;H;_pLtsKn~3e0M5t0cYCi@S=M+vIK+m9_60tf zzSxs~rD+5{Y@r6Ypjh^%KfQ>yO;XPfv^)BZnNp4aiwYe7iwbnobCO{Kjz>>aq!-{c zob*qD=)bu@*r*Dx;k^kBK!WuoCV5ls#|!9!UPdxrBy80zy%ovtK6Ako=T>E<$mX6Y zvM1Gl-R|>n5}a@I&;ccu4HY1;(i)(?>@yq(ZfY(mHYBQu<%_mq&=GeG?JkjDmRXvx z7xTb;!M(XZz|FK8C;L-Z_$8H8#=+iB&LEY)(7yXG@*Q|{NYP$f);i75EXpLm`qp(YmT*l2 z-HKVv5oc!P4j30?CaGF6_|0Qsi85_ryYu{E8gPPg6O`KlUVT@HhyykYy{JGh1fG@3 zTA@=DfFde2jUD}2Rw)Rotm0#`RE{$>zB;42oGqUhk5e4>U^bvt%sSHUB&Kqk(GO{D z`|J#Xc&=(dH6(MHKRn-SurFur;iPGGK2AS14;0W2;5{>bNad(Ef14t>+D)iDa2#`+ zelukLA46}Am&$?d)sP^Ry_E}9bs7dVVbBx^ckQ++*IJ?6T0bOI!pBbKk_xU)Fr!Dy zMXN$L)T&0LPin>?8pE^b2&gmeO{bvx=C0rp&pxydVq&=Wry)Xcd8VAS`?@@9c0;>< z3}gApO~5{oGF~OKp`D`4`tDMH^6{o9AG3K=SIuiQJ(G%vYR)1;cut~$gYW} znN;*gNfNWUiUiqY`l=xth4lXK)>{ckXYZSi6HI)?KZ@RoPL=HU5fU6I zRFs+WG4spneDno)p0`&tmE_w>?52jWZj-nG5^H(0apc3OzM77(i@7%oO7oV-(6edFv-H^7k_aMHzbK0fb7 zYUaCM)W zo7E+sCtBjEV3Q4HXnX?-86AL_ug=TYGxw6*R{8OhBagcc!(=#dehE|ZcAcM|n9|N# zG_7uKikP3sXC-@7mC#neZ>|5kzga8{iyWwXfGjmFr~z`}j#SCUXU=KM0?-j{3D8KP zwYKB=E5}Q>lllMNS@Gh1v3I?pO*2Do+4)nYX1NS84%a`zSzvg!Xu?FNXl-3w=AoP@UD5V8lJOpt~?a<8R} zzfbFKTGqb8tmE}~WhlbkEri?|DVVKpk)!9?L@xYp%O^ma?l07CozXx=fNSLeeeWau zA_JZ%%)%At=SQ^q9KZfJdZ^=P-sQ)>aGt-k7A$J!~s3rT7 zK*Vl=TOkDO^>l*?V~G5DxQ30c&Ap>1{>9wI&}nS~t#9?TEne>~@4c$lG5L-#UOlmZ zY|=+&TByxFb`6V@hk$S*kxT9GeKa~^L?iWj2&o3iGa{|sEf}A(6fY0nEH(KMx-oYA zXo51=c;R}Yc9E;F5IR%gUPhIqg@sTCs>}WbF=enwf2RgOcB>feoVQmA5+oXd33F7C zm&Mu>kV)E;WM5}#<_Rp?Sl!}pS559#ufhXFh96(3;mRFOccT(@<}Kn{k*(+AOap zpIp?)*hhG0gcYe6i}-`k5Iv&go-+I-OhjKd+aZ6`xit5jdu87u>4(?&m2b`>E_>{5&kUZ>asnSu=Uj(5YbTn z3qPAjuUP7^F6DuU*;*9Se?kx+ewxwu98{xU5OW;1W5p`$@_@?;!1iVYQ~}0(`rzZ~ zQE9~jWAj}td3yW!xGXCpQl9xLUhdo6f#?!Q8M8wDJKO1fuK?HL{!pII=sEPFM-6%O~> z`>08k>Z@Ryztjg;jl z9GVYBM{73Owafd2_h4gO$=UE3zsNb> zm?*yfUz>z%%kE>Z1&Pw~^WLe6l|U6Ysg9NNxKMgeDi$}w=%(F*8D5XNAoBIWi!)mj zr%iErj}?83^<&QQCmjFIkK^6S$htX5I0CQGTr0JQtk+(Z*CZIpO8tqce;JWZ7F9A{ zPd(=0`}p}4*{-k6|aN?|1R2q_?8P!pYaZGr2KY($HD&Dzn)xFKCHk6-lR7tbQ)FBC; z`cAansGNSn8BIweKm0A3Z)}1A61?8)z&CHVHoNZDL|FW!HIYb?yOI*I+QBTyQ2!h) zk!`5*tH{oKd?^M<$0lX{gA8%j@NE2pY;#`DJM<>4NH>WEi(qT;4=3FFjh0qOfk1KF zfq@G$ZwTOV#uEj)f3?r@Om{z*$WD!diqr;N2#ij*$f~RhgYy)C@REZQ%Ql+k zA64!+-^t4J!|ftIqs_*K%~z7ORD(>T(6U5H%V`rdCe648`x%hVzSlPzGbqSsL7pYg zq>2ua49`J^2W(1MdD>AXuo3upKMITsdhNkX@owEG4UlqO4RxcAV%)dZQgbN?--%YB879 zD;J0ETZZfH*#vsfvqr}3*J;hp`+5s*;iUFIy`9bE$skx>Pe(}Bl7p!l!2gx)uN`8l zz7ceJrl*MM)e(&IR?V=noDe(BkWxPzHu+8d?JW8kxAsM?A#ust<3~n#gnjL%Z=lbZ zu3sIbn$gXD;*w9>0hJok>i8=jZanynLq0B!L~n4;ll7st{|uV~Rvb3Z?wg4##g!s9 z8_TQDsiZ$V?-Y7`^LIkawQ#NaFk2>ALGhZT^GQ~Kz#jcEGTX3 z^!8wJR#$ZFj~Bh8;nr2#FxX<6kRF&l>om3UG(=RGTDPmJSn^s2{|j@jucw7=%U@N# z9A0rCPGPvFKGLh*gXS2Vk6Qk)(i(A7uPrbSBz+N|>EZ|iJ|!X<&H1H4;+uFxLi`9! zTuS0@lJ>zZjezT%Ljx2mKfRv%L`OIOeU~G|kxoVvYu9ceN%~41t3j>pe2J`g4OBhh ze_Rt-k9#wkza{wBbakma-BmB^a_wqsh@>|a%M|vPbai#Re9!YjtxW;mZ#inxsG^4A=h)?^vikzXkYrr^sCiI`l(@k06E@Ldg4TV4nH?p~f)ckgl!tYCR+2Y4Skb>e-(2e*xE zBcoDju>&Y>CLjYb_{KUL-iozag-7;n2l28d60K2YQfXT zyR0c}`8|jWGJv&f#Y&>Jy80|aMceXgD3pex(g|_JTE9f{C2Uhb=q`|eXxsyvZNk(&g6G(%@-1LpW*n!xm zYMHA#r)cZtv|xgt%x1!PDM1?~2xi5f*!J=~``9fvSoH@hRCRblyd3dkDuj9we&>ccGlss4Tk54oCQ= zB(KQe*_k_h`oACkwGQ$>fR0Ul=9;Lz;VZb!-mS6rX-z&jK2G9ew`UT&S%;_R{X+?N zVJEwJ9?A8!2a^%wTK1B&+HBHtIg%#HhU&|V4Z6|?(3fnexnk@SPq3Ojzf*vn)Nb(s zILBy6h(*qT-?bGJf!b*|PQv7Fysw>BET{5O@09T7m{G?Z|14gf+D!mJ@56SG1_o{S znUQ-=mp37;C%qo^JUFgaD8+so5Gs0cKJ!oN%=LOS@l6xe8JXB0WL80Pl6(j9YmYT4 z(gV`drI%H_y-Zb3ZC#QGZaAui0e4MB_67W6p8b}3FsIE5Gc@s{lmzxv#WfnfY1S*e z*(k+G8pN@8GbrA;H)g>t&A%RGR_`R_HR^znlyO~^KxNo$-1MECJqita0p2z*bWa%n z;+bqW{`oxu9&Gt`q}Cs$Pn7Na`#?xF3Fw2`u(WGf7}E9kPYd6Ky@Z^UDr;`XHdSn- zG7pJjb7vk1T*)UYn7AOtAr(JwzpzMDSr3CuQdF#ynRW_h4R62r9Pq6nM>y7v#`*nw zdX207__*r*#C$tBr;lQLqk+QTOXNkjN1ws_&o5q2CrEIi`iOW;SSys)+DK`ZXac2~gPEpEdkSBkRYh*$!;3-c9&9v^-k^&(#LEr)?r!BFq%ERV#`c6+!vM zSICMOaaXPB%I9bqe(19x`YF@2FEp*xkdgNWCuW2OQG#tF{%=?ZotQcmW|I(FhfQt3DLz*8r0Y*f2k=~*OL~`m%E=Bo^tPgT3BZBaiSSAP0tLg(9)1lUA*v||6VsimcaER%6 zFz9JmIc6pmc%S#J!Jj0#mp38Z72yx45`nUmj5Ns|PovRb$+sB57}c;(h>eziVwx6A zU1$n4tl|u?_)uD68B!5G_pEyGX`0KDuyr+~JEPKlaT)KbuODjCbj1DJ4~`QR$>n4@ zpy$`;imfSRmf&K10(~JIexA&r#@*XuM*tmLAZu^k)A>kEDUk1o;PbWHm8^S@PgooC zzCj`I$FW_Fjg2BIghDrdi%x#kiT%Du)ZF;ZXNb~X`cw-=>38qMpi5Qih#gX${i@Ef zu%kGupXJ5QFPedSAYAZCkFF(h{}<*V#R`^mp>EN%S1JWpyJetvn$}i$Du~uv`Xz4p z!%8xMA6jvQx)}H>WbP?Ke*gvy9&=99JqwH>u8MVR9qWtXQ*mqKc8eHK*iDHE?2MZk z9i6_1l7Hac_N|sTUdKbtk2zNL8!c`-eAW>B4w%fDKhK?HqiRBRFblYmN&kz zB3`@NYu}IAd;0ZGoi%qQ&RqFsJy;IEZAEMd>6m%m=9jXX(4!aXHc0;j(40_7NSe8G zRHA<|bvsmhg5z@ww=zXOvoh#h#-3(TqD6gJ|4Ll@c4v*}L!vp#`sY~4?nU$JPTEK$ zuInn8?`MGoE%EA0guc#0+=R>(SF2hNAGA=tsWGxLVnO;!V6IYoS2Yr_b^*sz*0ckl z8jK>EX$|CbX=oz5IEWX){;#;nE#P|%tk}2NLgf4*(?p#Qx7AkfKgWAy$**g?qCr;0 znpQtfo?h?@3oxU+yyf^B{m76VI*OdFezWtoQ|cJFx4ofrYKdsc&;}oZ1=0Z-%M)hm z&wkqHT^7sASr(s^|J+5ye4>BRRwa8^MUBgv{MOMD>6i`p!{R22_CR?Z65k~&S57+O z>_5oL>~R)&A_xsXItxL8$J=GmiJV)h)s}1{=gKf@6S2*^%*|Hphb2c&=OJabaMNrG zX<0w^8(QKyPhh+On=1_u^0!^kS1edW-Br)y6+IiDB9?S6Vad+z>Xpcmc71%Sx*ry& z=P%PBL;k#(kFQui`gGo%jp*QqrUnSpZfvC&7Jto@sY`J-Ye}mL4Nem@YC~v9Ue@xV zb}4Zubn8LOUCs^betuRP0Y>!){7EzTCz+wwG>(c2JyEINqEt1KsebxuBzJn^z1Z0B z(8UC*_$t!u;0;?8eg4Mru?JJP+)asknlBg*0lur*HD05i{#pUe;egD3JYmM}cIxac!=MY5C*vPo&1Wut6T#@Ib=aR$q$V3)%bNnmWw!z#;Thd8- zuwr=rA*?i)M^V9~m8-R6FTdIq?h77d-frEY|IpHQKmy*YG0!YoUT6s&&WuPUHaOXR z2pl_O5Xgxs-hYHUCUGQF`A*6Co8u&^5>hOFRR?w9dX#;|Jq@k!H9jaLvn|h^AO0Z% znT!y&1mP4nV<<=zPkK*vahNJm%PD3^XvQA-A;*bzqng;X5}9Q8JTCOCNfLW=v(gAR zHC=fJqWa}6;01f@2^Ab*-v{!zQUkY5t67g*QUn!GFKXc0pF{ zr+_50v(0$Rr;6;c7y61um7d|`TRvNNs;a&-Wd=ByVSgVzkcTgHCU(hSp6@nG!6Qe--ikB4hiRY!CshbvUXL$Aj|2? zjuPg^#*l`h$jt%Q55cw)jPbnXz%UZ;J>hn)t+i*XRF}!fF{a*YL?ntPev`<+ll)`@ z3XZ8zsziEO#vF~) zTXzg<9X-@oM_E#D^yD_CSg?6q683euLkmcTnhgQwUUUfQiiuW)a!x=4*UdhYkLm8} z27V)MLUFV+NGtAa2gA>|>1}Uog$z<#Q&Z{1b7U(5nvS>HIcFkBmK%{V_l;mR6hS7r zlyy5{pti(_ZUnyo)Tv6+kbV2{0Lel%1dTj^B6u4854PZ~F}y1Liayju=i-eHXJ?2X z+MuFc4nIX?RA?W$<}F1=N_2#1P9-wnVQfn$zy1xCR`mXw@}!pf+RO>Q#|-*Wk{sVR zm>LSxrAY&7hO8$8qGgYR$~C^BvK&TCvsC>)a`YALYq%hqjIcMi67Fp(`3*rCCWYFk z0vc<}xAu!GF@YGvPs+AeM)S}O7L=wv$FabY%CQhhl}bwwr`vIr(!Dy8KO3Ixwd+fP zmojs$f&ThQtZsXC$n>uY5yLkd6OnsdlEvbxTQd?**QnXZZNcaFV1<~7!gY+8A!oP!gw>yAI_<2%W_cxpQIWV{FPO^Tn1(|q{@YUqT#cAYqc zg7PBql^R|n_Vx`sM1W*Ka0uo7gUV`%ih5Ck*zbTYr#?c}XQl zm!%)NmDAAr1td(J!9gtWT-K-Jdk5!$GWwo`7ACoLQzV9%H1Ud?=?qd@%bL=@fL?S5% zT7IxRX|41dT7BB~nS0dfEaseIGo+R@+?^6OM9t>r_H9J&lBcn!G4|riZF;A7lLWG* z+F|L+5homLEJ?7QBT2`Xks|JG+`3yC2F2C+FP>B38!H)<0&+3>gMb1U7mo9#_9fFp z=mP?(D{7FP3!r@;ss`nRE*w`TA%t8j5rzw}e@qs?*ly;)%IN)XH0UV`4raYv%woQD z=*pQ%y~avVmiPAPT9&IRE_zs`xKl<^jG#LQn43I&8uun~l}Rc?KiROZbC2cvpxc`d zTwxPBq9dwyLVA+u**%@?54wt4l5PSNlE0BD=F>e4o_%9C6^?GAH?)Fb zn%+@;s=w=P2-%i%#}`uSPYXhr#*eK!3PMO*eUy2=1$2;SJXd?reM2Tvd^JNT@DAI% zE@eph6AnP?USiYCzvz`Uq1%zn_@ zZfcR1nc+76V2NC0a_onBGO*1{8I@1!{-Ce3kw7NpL{|*|u7W!gZL}Yy`S=7%r~Sry z;`af?N9d|y^Y;~*SF~xc_2e&uIX>e0CuRFdI|KtH>KdQ={7f3>It|*mT%tW+fU<&} z9VRGDyUh6q7PekX6$WYyKof;dw%#wN*ZLP(ldCs42NA{W5r^T;m=XA_(DYwfp^bzh zQft_xY}ih^gQc=dhH}FEh#daVCjRIfhNJ}_5>p`tpN<{%wzZbIezyGwA`L*2_T7?B z-`4;=lVbopk1n`x zWbtYmabyg2yP^#aH-ADG0=-TBlvJ;27CxSTpqlm>?CW6nVa;KqcauO>yhSke25`;9 zHERQ0G{Cg*khU|eF%hFf+}mN=n7phoIE%;DM2vMI~3UcfJieNVch_b{++d>E}f0Zf2b31ylHq1 zX!SME0~AGeOiR|AjPFnerrkQ0EI*SmTRM8Nr-0UXLN!gU`8|)fZtxcrh>uUsOyAZw z8HrwxLfp0{&>UN5?Q-8wX8sV?xR$5hc$mE&>9d-Ie>nMr`4d4H{Ug=gl-75gcB&T9 z4Rop-6NmO^s=Bu}grYpgvGoWY7g4Ik`C^S7hBc4P(BRQ`{M$-2c&fFjKB@DcuOnd) ziuure_n>$94-XSCO1c+4LlGX=q);}`{yT0+^s(6#X=lUkwGPI1Du)ia(P6TK4mx5| z;V0~8MkrZG^Dz}=*6XaA6@u#-sF}-y${6DbPdhuwNW}w>N%79rY)y`tNYL8Kuw=Xp z3#S};g~xA6KQ+zMHLDPzt;E`4;U=lg1Rcs$mZ~$x*ayPyxe6yzOnzOxy_E}E&B;@n z%Lc8!s18LN^!sOJY4^ddfpA3zD>2bG`vWsQyYcpSxOKMgzM^@XF{pk6YSJvOz@GAq zy!JWTSy^4a{M(njoCrD|HpJRGcaq8uAzSdlOG!Cupm-EW_$m{BV*X?do-+X|o<9a_ zj3}b%G?shPdD?_;3pU=8Ln=9ilCi9jK?Q-MzGWXGw4mLy7 z|KWm;trad-w$i3wsjt>&!$j~?$|xL02J{IKk38P>t+8e)q6S)l))@lDTy z3k-H$mx*tB9e%G{_)`Y_U!y)p%2CMPef}PJYV~iZR6FV3^mWI39nPlBX%hbeG6@r= z-fqvnZfe@IJn==K03|~BCtmnNm;X&?dGQlE!wCHs&Fdb%8jSrnrilPR@Qm~M4u9&v zZ1(lS=l@VM|C?+&(8^ihqkg|^GAt&zkTtk?V)Jh|ouhhv&ohL5Rb=GfZw5$MQ)7p} znF|42|vIgiw4* zGbT*LYc-iEt&&B@V0~4zLSdmN=?+N3&=h@KoX9l7?*t0mHP=k^;5RGyDOy;AB2t?Uy4eMODYT{a$}KBE%qTgZu9 z*0Hx4&kflb)%q)+dOy>|Oh(?0{(4-kUwu1WplEvf73u(6^DA@d0a++>Gsg;}+ndq^auj?9#WcR*nVYm2MHPOszMFha1FQ z9~o|~cdXlvfAOy<06x}&rzo-UZ*UGPcYU|$zgRU{N}TD;iISyssr~re2!{O18=dNoBQh5uN zBU12R13TBi^hdE+8;29S>cz*wJBOTK+Q1Tv;&QgtN!Ha@Hm>Qa!!Q4(R=xu6R@$&?&E z_9U9H6+5vT`Ei@phr$c%SxPAcneMC7`H@yZp(ChBnKMqW#I6kg4d$c*4%(~%oWOH)Tw5>^GV%wJH)#@&k1)UqpBzeU0>eeHo z-!~a%fI|_#H3wn~JIs?MHy>`X%KR#}x=wtdPg0;0M54MXiPhb@l`cv^y>b3*dlxWtaoFFV;J+LWl0Q)Mwp!h83lUlW3Fvd2%K;69 zYeY-E9M?`04*ZqOIEo4R)bI6zxoTrpjhyk7BY7Ten$6|IK>`a`u4=C!6K^ep$L)f{ z1r1fqo)f~wAs$aPBwmQwOL$TaKZtf*ph}c>kI0m|F+QgK?e@@B*g~3tSuu`?*uA~D zzKTNP*ZoLGr5Rs^M)*%n(RBoGW7PUjS6=fsTnVm6>6QfcOj-<89$AjyGOmzMxeB(V zCn8L(#)A?oWH*=lp{VE#^P_vcnOsV@P#YcBzD^L3l9O9~03f-v5AsBa5aS*X$|_u0 z@68S1I7tVw@W+;R^W%ql4p=37=^ei6Rqy#8zcB(9orH-2>(}h;Tmzkveft{$?W3t1 zv?!^2+G|8<(ztfKcpiUZ$=Fc&#%h05im>Vta!=20uB}0Jl8TC&aJ@b~P`!KQ7GG&D7@|b<%DBB%CX$AoupCJA5E{Ao^>?ahWVQ28Ns?QExw0 z9emaR>Ekq@LoJ6JPfgprdN1i(z5iqqX4g5c!1F%y$-FCze~o=js%g7e_)X`K}VX z@I+cmqZO*D*r+~P@lIB!DB$LYO=eoqThZ9|-g8T@Hbt6)gsXywu_K1r^pj{C9- za>5eIf-c$YIYwB|t%CCle4%wVue%SGGr4gK0De_~5t z!g0V)5S@ympjXILA6t+09kHy~p z%KQdxX4dLRYdBy zPIl5BxyPl_4*_e&CNk?%J#;p8>V~yKj19a<18rxGKDw>>G-iR|KGEr9&+3*`w*vfx zHg6Hd^|U9Qc{%}gjWzifpLnMH^vVkf6?ZSOpi$K{u99|f!f`#_f59KVb>t$D+v_Z$ zEQu}wr!!K(wc>ey8Yy~%PA&xaJ{8rdy7~-D_3ks8`}*D!?0GVmF=-*%xGFdI^;_z6 zt#k1=OIg#ex2U0h_f1=TC~9P9RUS2yH#PWc7xJ`pkyuageZ3hJl0F(;a2b_&IOgk%j0+)fhZ9D>7Lo>41H!T|i&hm}0 z_>K7f)hc>@#6k`5SGCBSKSaciy?IsN%#d~L{1mj&l-MkvJqg@95g z4pZDeB7+UCG6+i`F{}eX}q>!1vL3*dlv5_!0yHF1KEqeiX;< zil%sQiFB#GiSWRDUL(!OlCnOh1zQa5(|93|RuReRz?>#$w8q9V>LJu|8Tnm0X#Xi| z*5)8`(Su!parEliFcY~?UK&g*=f~<97?tD&@3SQ4z^!JoTJiz&iiI$QapuGZk7jjf zN7#i%xOU-HGvhpIE>>7qU&z4Er!QKmuilGV&q_;vIeTcqx0=o-H}j}5CH$n!?D9w4 zOaE;-X5B6#5hhc+_iJ`mjPWebYBiZD7?$PM#je!WDk?eDxm99wb|Gis(qvct<2Y5Z zu}HTc!f@S*KB5v&C*zkY5N!9Y@Q_yFWQYV- zt#LPxRwBVfg*!gC3aQUB_3V*lGv<9R_uG@C2Jpgf4n4&#^}nd7wcNgbGKY;w;nxrD z(soP7gm&D3K_X6yavPfSk(*P&vW$J>5820Itd(H7KiFETi1h!)*3LWl@X-pV<5TONh#F+CZhU-NgMSOAinYIrclOKEulQu_Txfauw&8LPW{xG_F5dR3 z2&V+>Q+|}$VUqN>;%0;52Boy3~oEJL$1HLLVz-? zO0CktS5~II-Hf*$@s%EZ?J0`g$v3Jo4} zNGQwQ@}V!c_@&ANe(23e!J3j+`*e3hQd?+z?sxg*)pJ29#t(ESz_oRy?i{jh^I9_! zbDv0JnV+{7-SiG8^shYv6uW&o$k}CwJmMHHh8dc@9~a+YocroXk3FrL7Y7c|WdXOo z%3JdKlU8zm0QPz>@DedHmgyFUyt1?5V)JW-cmJq~DWbu8!Bg2Eb6%f*Et6=85?&W5 zCHrNm22GR|b>56kJ_(Wh#9^#Fq6=kq{Xn@UPo=o8SfX|8kYex|f_Mcml~l)9)}e)G zy@h&y%G)1^yjvXcGWu9KZ{arf011VRqcIcd0wln#?Tq2a*5h zI+JCS&9h_gW1fG?`dJutd}05vGvtNtkyqEZPxS!+RNyC%3UVkxYt7h-1^!&yHEy zhQ17KR7C-ghZo4kXR9tDD-*2%SA>W; zAE>^z`?g=lhOoJ!wUdM8BA!J!78*=Ifr@D90f=uxR6N_=@#H#Sj>GG7iCtt3N&G3+ z25P90vkeps6o2}ZRqIjAOq6trZ|HQ{C1o6kzbKWTbeDwBdr7h1Z^yS)@*)h5zNF-e z+HZ7J7dj;)sZK9W32ut}J`FDV>YOHx_09ev|F>C~Hhyoa`XFF1{7q_)`P@ zqc3&M?kXR%zl&pnUeW^}z!%c5gh6Z{w37f2w7og(^0UGf1Ka($wiW_SE?%{j&Tv3+ z&t+Se8Pd}?@<9Op-&CR5e@Ph(a5@JCM_jF=wgFFhO0YuR%ToAeyO@CEO9Ru(ZFlp3 z9D@)-WXq71NLU=%RFl_r)6uuha^w#m4=;ptTc$A!q6CHP+TsrO8e@NtEb$QC`ZanJ zM%-*QXNZ_S;omOUb|6LmT>I{EwEkLpz8s_!dG*%*-2r7RJJK!Ovp2@^^`OjxkkCRc zgVgp@zby9pqfCsJ>`AEfj-#tW`+douqOJJioaoz8e}wL7NOkRQ6?o#qV?@B>r)rC1 zDHMY3$#swLl57tJ-(F%)zAfF9VJd)!>Q=e)kz6avwjrR&lG|w=K{8on*G0KVa`zLn zQV3;Wa<(Hhq09a=Vw6|Xe9#?Tr&yU=#66?8m|HnE0o~X<*!!wfyUACqhXwllPLS=< zT!a3h%R)nbJjWfjs>T(f3GC1tDJ0wV4{qQM770bbMI1Q`rJu`R09n(3fx6k!mJDtD z=699ZP-L+#3B%n@O5+ExdIFOp(zyH6vz=9IRpQ3Y?44v-xOZuGjuUMPTvICIWe&{n z3yVqb?OlH6V{udaU6cdIOa&n0Aj|(*p`hd^2(GDaG2eM}m%ZL60KJbv$1~Qeo$`jP z#9d4t?+~%R#?QX({JpAn8o$i{P;)77PbHIIZcV1+JkOq#K-vxP+c#(|m=aoo3;k;j zMr{>ie7{3N$AN{i^fBL?FyA5q21b<4%c^zDOG1U#)V{#V1PHKTJOKB)BP0XNRzNm& z*pjfv4s8w$aZGJGz=y1!nYVi1pcL&iFV~qJEBNY{4*PhSm#(z^%%@H}&N+@R5uZNs zc(u&H6j}$;sor@1C4uw%i-M!GZ{wc=r8fJc4CrMu@Voar3U&SEQ;qh#pqkn!JmZcw zRL7S9fz75v-y{7fW;12)OI-fxpu(w>Q|+>nJ$1CZOLz6I8yZR-?8WRudGDjI(p{3c zD?W4wlgntV9o2Xe{0Y1eJ&5uQg(R6(6vb1_F+8}1k3HMuSgt=_g@E(9 zy%mm$$RieKxFQ&@1<2y|ZdL;anf;n4oOE6%k^oY6M62AHb{HO822h$Dt|AQDH(oLUS55G&(ltYr~B%6bbvd%zfDek zi<+)51+EV>Yhr61H>OB#jd~(DH6`oh%}C@33$6L1(lE2OE4i52E)sazkN4mC2+u_h5?dG;$d?#lfSu`NxbcuvUCd|8jwLC2q4Z7e)-Vq9nI1S_P ze`m6$PrY@7?#3BE8?%A*@D{-AjDV%awc{%j!y9qpo&=PLaX4Ttba)j#1p?-lraKp( zZV4u7KRXuChUT2d-+3Bqe<$POzj(Nk&+G6Bm+n;$WcT{t%F6 z=LYaipi2k;(!_m+WfqaH&e^ASFsb8vINL8rpE5M=WaHt2K6o-9CC~q0N2-c6!H8l-nxoT+aGnVvtUox;O)dq)X@H^c<6^w27t9Xsa-ZT_No zUV$yh8&y6Q<%F>d8?mvL367Pwz~2{{2ICY}AGdKORHMI20{BnyJ(Y`;ClAXK?l2j> z)(|2Fty$q^_UhUJ)=LAH`li)lGLZ zb*brNM=wNFleL~;UWexIV=4Hu4WQ32ZE17%VBHed9z*CY)qJXI7L~Z8s=8tHImEkO z$9B%Hs|Yw=OoCI!yy5LkZ}oat|AZsPFjfnEin4f!rCG$agjM_C5FiM(i%?2mnJnuFMno;x}32>Du_@180@=ECV#i(z8|T$_)A6t1+869#&owu=$r4;a{LOx~yZ_%8U={v)P#>VDuH2 z(d6tcy`T~CgB|~*8wuG3dG8Y+>#-Nq9v$h9563mdVJK#kBp8(XW{-Clo+Sp`B|0tY@A+sPcJkb>bs zxFgMJ8tjJ$7arJgN@V=B@mm2G@&vbu02 zwvq>&Xg~8$IDZiy`ur*3KJX#S7nofe#2uYjZqDOTdWRtmhT6gRHt3p5PdwcOylnk^ z$c0kG)6;1-Wky{~!pT{yBPEaw!K66Q@rwzrrE@^p@)SOCfj{<8Sqmu?{%~N|rihOD zew+#w4+s)!lU(10MJ^d|LDc|?XMi%mZP*8zOJepnOM8?5DKEh$Q zd_Ag|=S{<0lBL@MB_U&>hP*d=N-1(IsJ8lq2Zm zg#3(Mmbo|uFDujLXV|ibewK+pFbb4uahpiDu2L6*rjcnSq2A)2I`vW#$zU2(Z5V|j zzN4snHZiK!MbU^^dG=dh^(#_616Z*1c##av~^+@y1h%9K&ROvJjBi}-0)20jYWS`GU zed>>>UDjt0eU==sWzV5Brz0ZB9pLA#azFSC6#(i)`wlJS1hg%PxDQ*CC$f_AUQ5ouUG{^&LBX=u#BfAa1jC1CHwA3UGZlpmjj&H0b%Wu zkXlQQE3DU7Hb5_H@x~}IR#c!{-^FHuck_NTQYBizZI!td`$Gj8+hYpctrHmH`SOh9 zx^7GKo2H@kS|nSmemv@0;D_jK`rHp_cG9&{6* z@aMfD!oa@BPfSpX(Z#Opp?qC8T(p}Py4YVg81Ca9Zh0JO=r%0frKy+Ncgu*)qH`e& zh_oJkv$UF{k%w?AE)_bX2aIq)iad2&MK9a408<~Wd`LFw*XX{=$^&OC1Bb5b(fkV5 z2*d-U&!^0G@?=rRWg2HZz};7Y@P;FNrip%KGW?7|S=3M0I(oSADDr493Yxbe9N67- zLY3$NgC`GXB%yLmV@v!4Tu7WI4MQ1$jxj{6ywdG#$S*lvLeeldh(XMF-2u_O0>0Z) z=i0NV0zo$2V?>_KF@*)l%7~=J32~p|T8*|IqFUW0{dNF+SetJhzo`ndNyUFH|f1S`} z&L;=Rb-7`k`kBawi{4;xzv^k|M6W_HGreV|$dP8fZkE{Q6(^=a@V{X|LK~1TMrg|@M!V!7jK^pnw&!sa(?12?E8c(5T{}E zt;w-78rvYBFR8F&2$c_g9ijcZP_-a7*?5YhwXH!6Nht13SA+{`+MBmn)|~W1`Dak6 zdoMBr0Sk=bG*HA79`9FZ%bWL|)gESuM``%$)T@DM6x}%?f?MHemgrs#<&dvnmMNOv z_qpB2tnk^vHVuH5c(k@y#3BZ0H?}*jeimTP$;QP}WGa|lK zt?u+s#;$Q7rLrTqpYSxN#N2Ub&MM&)(0@x4G>FPdE1y&;MaSyYzvDKOMQBKGa)Xa9 z8YbUKw(#nHtLxT}Tiq$p$$OuM-o+GQqa{2OWx5J^nFqreh z4b3s~n5c{A=C!X%;r9mgS)T9lHq$x6Ci3F~RdWeegU=hbM!JSZcOloloGXV68&Z8j zR`&@TR|N-|mXBpMr>uXp(}9KXIlHNWKDP$7@NJ7W_tSvD8 znu%rA&}~y-WStn_6$8-4CkRnA*hZJeV9V#WBv634@@# z8nv)BJ1(SY>rhl=dYy^$lh``TdRzccEUeGT!edBmI?Y4;2Jq_B^}4g?K{UMI9Z>#S z9Tv}6L@f+>O!(<5v%b3&)=ubl8WS`d@;pJ!&q4PmZ|G=gAlks>Dx|e>vbXL*2Yq`3 zMkYA%SZXxYsqnq1wHN)e^JWJq*W#cA-x4Ob6ZLVySg-S3m5pTLYVx>y79;3Se`dev zSst8f6ZzRO#S0AwT34Pkn+0C3wKs>~d2{ml zU9^X(G5{z3%W`AAv zo(&t{*sXR9t2dS!7EUd>TMXhH{N5Z4;U$)JC=$M^esBUctK)Z=MwSZi?UN7%Ncg-3vN9PQOQp@Nr3x z->>DFZfv-E4z0v~IDhMpu+wmCSHgFwR3t%06Sr4fZWi*%Jl|m61C?a`2!2sTfEgKE zQBE;fm9JJyRxIaBy^UWCj1^@aW=wftlC@9e0q@r@sCs+vF7FguGxofT=Rgih-`p{8P1U^*kB>k!8-H+2O7wk%4cyv2{#b0TX1+x5%cHQ3fp|DkQs%=LfMxP4&-8D2Z8cCj zn~4o$v7Hwc48|oV(Q09m5zqbwz5I(Q8lwW{;s+NV<9e&!OY*j=?&7RAZItAW6cKK`9{m__mf%cpTrHg8Y#u86tE=j zng*gYyY|2S{n5G5kNLLx`yla{SCYHPnhE`l4*u(V?~kBpmJk+b3M_rqnG^yjtw?PD zn6vxWTmJLH4~p`&g+(TUSN?HFJL0AloN%OLb^EQ~&RB>kE<%tc%A7BC@OdGhbs&l( z^RHF=U!FQ+cP5%L)Pc8OLqu)kvav~;|HpAN`oBU}s<=xDQIb4p``!728}Sw%^~L!j zIo5ON4}SRFTLL05-a5aW6nb^&8rZr1p55M#c}00cXNf1(S&ZhL$^>iM_f4#S(T!i8 zKaFr|S)kJoiAa^%cGaAHzmHy)<-)uuJ0EAQZQG+^2+S`ZsSoP9Q4*yq_g3$wToW3q zTm?YRaFbUbyFE~o1OZ`faZ6x#PZko^5y1+T<2@_@PjbwjeIx(VtM$c5XyL0^)($-i zZogph?z0bm_!-?>IXMO zGP1NexsDW2`(dV?v`pN_3NnwdQfc3uy^rFAdPuIDEIKbb^Y)H{N5N~XA^@G_<_1+x zoR19TyKxS?hT@OpKVhspm=VK)Nk99u8E4o73>s7YkWK=LNGa#jo+DbLU!-Eb;-94A z)tzoDHN`H=gk&s` zV{iK#fs_(jrf}qZI(2q9;E2NsASjmA9E0DMNg6x5G-H~MH}_K8QHLr`T6`N);A{zF zij9P|e;GY(msa+YUCiJ*c|niL)%k7mJF|lR*AIHHUvI8}SMC97OfRM$%Fb`k=EpyU zQAQn}5MsRi(dimP^Kq|+sDZ}+TNWyc;~}ZR6N*pIgmaNu0BT)JMVQyLyH9m3b&i9? zY%f{$u7^#70$kc7qiP0(udh|;!u=w^y>{twldegQNpO0w%5D{y5;9h+GBb7sTf3JN z&G(F8+L~Nx0OdPl`@h}cZhF^mrRP2RieGp=@r^-rvwp>kw;a>M4b&?lE>F6i=i&;J z3`n^QJg!%x&JGQh5hHnVt@8(7xX$rE@xsV+cu_&qJ&*2tlVQ2kdgDec95KYHtH>0! z4-+eaHJ`7|Oz#Btj^@9*(pSnFm@y@JF~K8ppL~qdpOsX)%(bnF6PZI{vTuHh{8a9}R3m!#AI5byjbEZOu8bW6pS5(BK>VqSeGr6Xc*i7jnaxDz%Oi8p@^ZHtyN2 zU4eAqCvd9W31FPKeLfFtG#3szXF8nu1^SZ)+)E&d=1{2{-SKXdv`@rR{0OY!O9DSc zdG9+~4Luow_X?*>aG2w|AZ}bwe6=4Jsh6N^?loyJ>cJu^Mg_#s9|;>CU z7XH3K)0AP@gf_1*Z`8*RG=O?3zq4SuUkiGREQhAp{$pxReN4XAeQf*4Chx|&BCqxE zMo{%FRh;*=P|Cz74DX4>$dUY?8VoM4luhpR76|9<1j5W*WkE*eqRLj58=vj!aw%yD z70{Rkn2$Z3-EABuy2%Jf|FDabbQ&5e#q)=*U&bk90JTv|v8^16d6T!O8B;ujZ7;#+ z!-W!L)nwm2W7KvfCZ zz1qnJZ{rHc)CeeVUEbozNSzRkzh9O?^FI9au$a;&2n|pk(UFaQ8sfAjn+6#)_7?}^ zv(pdYf1@d|rg!r>bX?t>jHT$^QlWl+x2e6fv(Fb}m*$NN?NwzOzrcf*DAG|I4SWSw zp_i}^=<6bYc3r0mQO;kuBCXwhDbE-J;Iua2D_u}~I@`*;+$UpS2KLaiI^$OP!XvoP zaxmWhfzQ6NFj0=%9XU~dBJ_)bkl3ToAL$R0C$*^Ija^5W(34Gj?c=LG^I{;}yCxdr z285JpJCnnn2~GV12W5cUVKo>&``>}y^L~##DiwgwZ#OQ{xFJ4kT~JQp=q`z01KFb5 z(UScyB?c6G2?*c}q03P77$8T;0vvL+bKoKdW$}Kwi~v9*JN%m=bPfvV!jP9jM#W~Q z6x1C*+S*AZbk`a8jq5AH^L~UJCB~m-Lt;`VksRIazc%o+!#+K)-$^>d_LXV8u$HS; zeiv*se~vWl&~yZQON8gE@pA`Z@j<5uI&$}pm)QC3FkzdB=yJt6=F_jCu6y&_A14*Y zXC?6_2r|Ta@OPA{RZPk6G|%?m%^&q~O)?pIW;o&GZNI3OQr&X~z#R($HJizloy9i! zRUhey$rWQqC6f!GgXBqNB2k5$nJQ;?yT;3)p}S}6snN3qapqLq4uSN{^$k@)b5C{# zcmctxzxlP*TeRc7_=s?qt|CbKL;PGz*-r`vzpLY*tk-r#`O8~0CO2@L4 zPqlG=ZX1ss&j!6PC0xvMY;opuHSYXZYOK!T%r^7E#%0#R8S4gssiO$KsU77=)b_(n zw=(|~9=4+e`|nAKcrFG>3tq(gj3$Ypi_D)u#BVjB{pXP+%Q;uTtI_jBbw>|FH6jfo^rjWw2;&Do132S_0;K%E5;n` z^tnyzcmp(c1MrK&Q8*l_M%4YdyC;{JH#=;DEf!Bp@-E_787bvyC3N#`DO{N=xKStm zHQlh!Hzi&2p67CuFFB2K{{C20RQRH@X17YHB&4#skyx*Jt(yO|uhnxbJO0vMS8??1 zEI!w{2(8!&BC+aDR9Wk%!XXC) z(FmKN4OSj$igiDje>A4+=~3l6haB|ZjBi4!zg@j>qnJT7`tXpW{s+v+A0%b!v5Rf% zqF^%n^{MMAE8@A$Tz~5n_wC5n+Np`}&ulp$^NMG9C6=*zj~g8Dt(?%t1qQR})vNZe zlx|dJ{b9;}RmW-7p>&;M=<%&AV+EU- z+^HbA`@}^IG;v1A)oX9p8ar}xZkX=QeX`*PUn|B;>p#(jII%AEi1v9U_rtT=*4u|F zFiGa8aMBMVd)l0glg5vid(4{89@G9UW;9#cq-`h1h>*psx5gckF%9`dfS~m zu!kZ0ANZe3%4DLxLNOSN>yBM&d?wT9!iY?Xn4of_+#1}}wRV&y8gb^oR?JMV$j}iC zZ`_NtkUeE$pj>(~m9=dlz#azJBp+MRfY-d>s`g}G%{!w%IjQNoSWZ7T{$FiWuzK`jiRh_u zuD*|z+xcePlilBP+(_NN#_9cGpXdA$5PBIqx!Tm)tq=r%#x_dMZ}+)g`1(rt-W|GIuAf&+tdO^(eW?cB`u51xThLqb~n0>dXssHrNk#>kW{pFjR&?vW9 zO*0F!O?P8DXuW>P()Hq9i?mUeH+}R@7aJz)n2{3m8s56yO+Shig`s*XmnDfT3E%mE zA+mtJ4|4PZg#NwF#RYxZ5qcBcEXUBT<3oPh5uG6(p4oPc^{DriO5J34b?+cl1GXS! zmp?CyHW5cg}3=*y93qRDyK0GN$FH`$ND|hqqkG^uK-KO^RKc#-D zet(0M!F8dCz_rt>vwx(Rv7lvzzH%P_h&%4t1J)%g)E1wmNGaXZXZ9 zV`iExR^eC=04Iw^j`EdoW3Ka+933kMQL`WAI~R=>rcI9K+75!aP>Ww@MoSMRKs zkW|mN%ZrzM4hT4Jy5O3(`h}YdYSNz*UY#FuG}#E7xY@6+uHU95x&0)w|Nr!J_P=1p zV)d~B5=+}NtzAFn8Q<@Z7f*JX_6Sm`T0xc~8B%MZ=e(&)ul@AZUsuHhbcOCpWeN$v z!il*&f+p8GXsId!r^Z+ySdBjmAhq!RC#fXK%;r}jkgpH$R!`fhb~$F{;X_}4FP>xe zEwAeo0Rpn|93w=INowi9$7cwQ@(86VTSw_!SS z_7@9i7+wCSk4_~ks*-MjpBFdoX`@;b8IA{;`#iN94^igov_En&wSllZqpB0eF|bM;K}jJIrXATRq&>98M;F$&Z~{V0E} z!lwhf`UrK+5zh2zDSiQ`8Q(so`EYLwI>;8f%Th$X#V!Deyk%=mhk27tpqce~hq>%C ztCbGDQk3e1Ku?|D<=Ys@uI;vzEvV%)w6RbTZqI(mQ_3R`hfCQu=p zt?3_Kyl|y$E(t_Ke;I1U%eF&KudO_BN4;5je`mhC9XF*le?8$=A=N~H#*cHPM>0d3 zns^8}ArVL_xWPM{j#f?FILg#Jq0EmCz!9FKiQRM?S?YCtO7Opp*a*wt)2q^7s6O&n@!YuR;ko5;Z&0 zcC#?~OUB8SJALY9i#DApcn{Q7-U-sqRTnc>GrNHPsexgFFZt5VX{rUxre(C`8-pZ= zdw)&!_UBFQV{EWPni*suYC1kA2u_XdJaY=SfXprhJP(f{$M$n!7c<<5@#W`SMdib+ z+L`+c{`+AltTyp&5A$?h7Wt^-c(G3NXsP1Co6F~VwH1kE3eid0IF~lByDG_!U4$7T zqqI@Vxr{h#J6$%vQXi|6MV;{1vtiW10I^TQC3RoY z--(YfTj`A&6-{ZBpD*xms?tJA7F`#J6BX7{i3pO%yaQbT3x>UuJ zrLCPZH$_9{vi-3p2inR!kED{ulPH7g(gGLKy*zS+Ui1f>bE=Xk9QF(l&irYmllKb| zdX^8sYmGWC8CKp^@R1Ll!WgM8(_AGV098j%%6{DT=NQ~saf+UD*)T2@I~TCJrw4Ir z#;bgf)wrd-iA;1yRxBppWnII{@{VcIynN(QYKvG;U%!|7RDa10KUa83=3>&aNbC(N zDdI&_!tK%dV$W+K592AxMzLh7<}G-H*RLx~JV>ql7{@2)04&4YwtQ6lCz0nfeb%f9 z`a?#S%tgIXN%lt*iMv$XVQ~v>SshFAZY5Tc=MLPgcLp!Ws@b`_IK5=``=I_3gYnJx zql8PWdNx~XtOGMg@6WsoM*qkJMo2r}W0b7dKTb@WJ*j+3B9w_QM{nlL#hX9I@>@|| zCH;9KA$$a*&GEFxU5H!bx6=vo!YE={C6EG5{S92`CJzuqF+jsk|kCGW|2#^lG1#hx{AtgsJmmPgBaq5ZhA8g(EIJy)h;sJ(50 z>nMoc(#9JoAYgymaE zldl-Iz2HP&N*5StxzygOscnVOlND&sd|$FZ*iyP~ENZf@v%5PhXFXF<g*_jmP@l zk4o?Dp_xMbOQ(;=rR1DfG5BuI3IcOUyL@^~Vd__q(CepaS&*AzWTuLW^*vglQK`sF z>&aXA#4iiqTZ7L(ORya9{7=X=_!Z8Vy}vwul0>a<*4IA=8Rb~OorcUl@NNufr4|gI zx;H1Tw=->0Bnu|IGB-auDZU&>>YW_5voNz%_a8gOaR6<*uO+NldVCL+cJ9ld(7W68 zg^IY*dK04n92F2_wJ%z9)=#P~W%Kk8XC3q}XWcoP@OHB3?^U8`hR*ngeI#Gn z8MFnAPQ6;!j4u#*HxM{=NomMBwB~k7g|n3*2#_?=l0^iHub&a7bZw>oISNG1s0wKL)C(=~Kr*04Z;T8PJibP_(hcF4((Us) z+O=sw#or9to8_)Gut19hJ*ZyNZ0K_hyb*t69lYQ6567LWwDH!DyB)}+pB$GvPpn?F z-%!$BBgEC%9I(g1pCUxOK!nTZDhcf`yxv2JJbDD}T!nXyySv!x$ry=2VG#-1^Z{Kx zK7%y4{9)AJQBME2yKX2{>g2H-5L&Eschme?AYlV=%gf!HHgPpNt&u=W%ejKTLRdMW z+i))4WQOCCYE3{2_m0m%{bO2*qb7mSx72aNkJ)v;8;E*<%kEi5?hjU8D@JbJhh4;4 zkED3-bi#M%b=6hJ6t|@B_6Plnef}Pbs+(SAg>%=T9D?nqb}{G@Z&MylY^9idYmo8h zEo`-?jd{R-WIFEDdvV^^cT?lAq%8;EtyP{;xC=-w?%GppaKi<5p??la1!d)4z3PKo ztTmvO`M!jbJUEv);@vHC{~4Ned{2&8+Zd5IxxNWet(0W&q ziJwC@;T7%dYIAr-AV_ca3Sk+l4HEHD)(~CcPI^hhPTBj@Y1i}S?U~SsXI?wY;9)|> zL6Ous6OW7QCCVE>T0m`hl78&-#8m5N*tBpsE?rgdB7xI$Bd0Uat@|7cAwBoCS_C3a zO5mEs#O+HD<1;bAFPn?0G5$R>C_2EA5I(o5;?U>&TiRVKm*1?(W-TfYo}mFK`yeKM^5OhQZ)W>*%{m@zR!eruhHu~`h$1NWY~OIH{bQcy`r^Sx{Qr$ zIM({bz!~0-P(#ps>h4e6#6SZ?1NuQ=%K!}JjvLA%kJh=JN3-SYAie82*S~M9dGEt^ z11;BrF+k;BIp3nF3CGqO^}$D^vfJi;;B=n$bym)z1^#EdDSd?Aa(C%n<9m0x{2=Bj z&Ryu>O==TdZ->^@@KbMx2G`pyiX1S|ijt0OhbbfM2#3W#Fr^K|P31I{?qwU!V8Iri z`oVoyncFZT?6w`EeefX%xqrurORD!f<4@krwZ(#-*@bXUSOyIxgR(Ecg@(NIO?LMG zhu@C06OM>|5n3Nbe5ytoIx!B0Y+nBySsGEbxt6*)=lMmo+GoTgfqST1gmf!T7Zu%O z6^T-!FPvA}gH)SNNZ5I9l>&G3#Y4Xn1DJY63OE849iIeMpxew<3X{V;KWv28JD9n# zEuq3iL!P9D9_NQK){jGd`m)nXfFF&z=w;yp&AJr9=9 z?4o~K!`VY-*18JEcpheLrZx@VF0vfFB&@-NR%pTLxU!V{iee*XKTEX7B&g^$I2-g+ zk4Ab&aY!#U?`3@oT90e)@hi!0eyUp|PcLd?<2VjKltO@Fjo{J0!4-Ig{yK{e-*r|c z5e#e6+b_Ty(6ly45+m(cQQ;@T8pdoW&*AL-HlI zg19MDHISFBFXOdHXVT^!oNsGbtw|g`Gn>aEmSy<3X)mm-CyL*`zi?)f(sZN}j9^**Fw zFsyBtma7Y?M5HQ1#HU|TUk`nJNHN&e-f&jHH`qtb#F+ue50BB)!*=DQ#zy+GucH*# zIm92U)*hC;4~NWfAN_>P6lh_bAjP6jDy}7KbjY;&YG0}f8csLaa!*wq-VHx8CF67Y zq#;V9)mUy)SU_%vdEI4;k-%a@~Uim{QX>wD!4 zA7rJt`$Dv>J&vQ$RCG|>HqY>o83*|`NlDp;5}bV~=#syGeY(v+GJ*FJ$LO|LP4cGd zE8=aIiVSf~)Mhe5y!3yW@7BBX6{{gcG4N0^`N5H+gz+*J6s5Pg)Ztd|URnFK$DY(+ zq&6_WO=)Sm@L^L($FuGuXML+>2-*#upZ*79eKnxb{9#%O4}*N`4-cKULfd5P@F4MT za?S50V-_%EHQYLik6`O)i{1R}M$ z^|b#eu~z*8{pU58UW6P_HU8nigC85IVsKaiK{ut0u*>oE<>W}y;)WD=|1`G37TlDW zVH>+IcY-R5T+M0;)TWG28j)^jFen-^P%(zVM+)yv(69H}NW3%MM4!}vHyareRM0jL zlEgT88L(nyb~mohZ+)^^rOkL+$DR)+d2o92!t`7J3rV8n;AlHFbkFbH%*cXX@lWoZ znIlA+AjAXTCAds@;b`;L^ayjXjmVOG0M}^3kudTbHUMRY1P$feNCh*4j@b2MATInw&EdY+B zg+ji8BaKLU2~Om6+cnRHd%x&IXAmjAu)8T;f!px8^!1k;qG;P5H6Y#e7(MXIHYcTN zAHY-+!z4&8;U~-bNu(xd(fBESN<8e{boY9p3VrHR@rm0*&*4FaBK)%Wk!WlFMHq6z z3YdF?n1uI_ufGm}F!ul8GVpbT!~yCf`lr+I?^_I&M4avjCXWnqr2-(Yx>f=dCq^T# zof%e(%E~nAZ|XT$3{Q!i>3sevJe82g=rd zsNVjW&OyW#8MQ=G%qFf~=2ySbzwCVhcx;F^J_dnYI6xi5{{g9yA~MD)Pmcla4$J~k z3V*MLlYEA9+$6KZhSujdoM~AK z?V<1)2_U9`3g$8PBOh;bAA^oi|3I_9d!qlw`~EmV?V*{{f>Mh3fcU+Ec^Vmtu-{*~ z*(Hqp<2D(=`9^NhH}W#2@9^MyJa;ai!K~kcWWoWTqR_V~uUO=+Y6E8lLr!S^@x1?s zn1k1eeLT>&kNt5T`FJQVI{mG<{@1-gL$`|hVB>X|k2=T&KIXZ173F4ewp^P0^5S=`E0Zh)1q2LoXS3EJ?XAVdCxIDNozX?V#g^&Q`U!0cM9X#-fW=G>_ESXvQq#|-g1 z@Wp6&Ih}l1S{jM07G4GJV~(-7l@RNqY{Y*5Jb_Cg+FwS4po4aN7BfP)2bBj`xF#LF z@U@IWVAl*TB3c9A?agUxYEdEmz$Vhj6DWmDVAwBExMeu zp1ohb@VzqfKJd*ZbmKCZdO8E#DuWE~TXJ{!^b!RL4u4J#Dr1n$%b!uaLCiCs7$_i_ z?68~+O?-4q3`dZm|MlqD{lp26&kzZU z7H@u!T-o22O z*o1BNVfF`eR|g{ggiAZKGbDOuBlyeHsz(}O{`x<3o($WD7l1cwC6;GCJoGN0=0=k^ zmZ@Cw*^HCch`mfRW)vUB7UH^miAq`TrVu1AwRhX1{0L|jY9!F%>u>*9pZ*ejY=Vdj z%-^2UWwSgde__#iFlxW)_Q_}Bx*ez6MN^~&GcdYJU^S?!&pXbTt-Q7Kp#us9BB1>r ze*!?SP}&zbg#9H~fn_uS6>0>wR;}R*&wMp*F~904_N&WcB%bY)rSrHk8;q6}gQme1FlJK4TT(?li%_2Wqe#6MyiK!#@s;kY=TcFMl2V4=iDxGHUHoTvYF~JZ8HtI$KX^cUqi%Lpph9htGrd^Y2t^bHTYI;Qz|<-D$CBL=ZGpyQ7v?MD zCgDlPP*THi`yCw)!4x&>PN+OpkjqOKn?1lIU~8=SM=DF}ya!Sx#8LshmVB8xmoW>) zj!d{t46CpGmLGZ5fkm2(746Af=54G@4JZ#j*Q^)9UAHJV-6x2!9a)Qk_fM>hmO5_5 zNd2e)bvI{6Zz4lU{&nU5x(@>GanW@&vTL{$5tvahj>p1k1WzF$H` zwlj_Ce)+rf?$JH#vYu}&d>=7KF_r7UPjEr8Fz!^K$E9!0T}kRhP@Y@C5~_MGPqbfd zV%>FI|T-eGxlb)WT&H9xsJj^P!!n9oRO) z=-~0WqIQa*p^u3Pb{#SqiR*tq6F<+)^?MqPbR}auM@dh|I0QU5y9OPtlPa>6@8gC# z{k?p;9Ok^?2gtK3_Gmk6X~{+As=3}q*BWhZ72d(TP&h=0`y~5Z4p?+c%ij`F|B!}5 zN^{?#o5>uQRpA4nyrco_iHgbJw?9k&n)aMOcTFN)>IftLDauCGP2_goyaL(xvQ%;E z`D0v;273`SRN;mINe7kiB$Ne4x*v5ng18_&qjXsz&bg=N`XZ6<3`H`Rjhv8|uGRt1 zPA{-SGFJlyk0oV=V-fAv=|R zSMl*(v2Wuq^`2F~_Er3i&#HlD)}WR~^Px{xpoFTLBkY%_ zH}xg=Yt3VZb1)aNF*;b$XhG;|L-0FiFd{C2FPX+GR%~iRVJ8G3ruJ&6jRk zX6x9oNn^VJS!Yalu^DvOXe1J`i0;hEo8Vri9+`lO5ph*38yYX0$7W}tV{y007N)(o zqeRpf6H>oFL{ju1taA~`E3QWj+>b1^pNu`rbWJAD&jRtWOix=MC?_q>ixFRhQ|Vaf z8=N!r$>N4m)-4I7SRHjP5a1SCV)?16uSoOnYpRuO`EB-zcRncgyWiyWN?gtjWm0E< z%R4I*a%@{qUz2fLlm$h*1P@5bmu)O>d{MUV2>VQy{8IW!<3{lzB8CvFBLC4cVi&D_`XSQ{CLPH&;deWpwXY+7<;p?;WL7;=5RK znyXJ=xU_vXN@MawI6hC!yOHB8SQb6?olxcXcwaZ4iqCZoy5BY$i!N$a<*V=rA~qCB z{61yaw;*!)a9ZhlV`DmqWq1A<^z@6Z(-+@#Z~pY(5OLfnELT!jH0D81TJZx zr{+^f6B_|>arA{lM_*o>!$KIsDSW%j*68OzFTHS<91q0xxUaOVs0Vei$o*s!E3I=E z)B)>~?=c|}9Zy#SK~CZH-{*6$3G9{Hm3^#x zV)2J3`u$L&|FTY*bK9_BLKl2x?%RSY_eng|nsMpwidQdma$w+ANC- z!HH@oSF(}J8F7Y^kdS~%5j(tP2tN9FU6V5Kpr?qG*>}N|PYk3NCj$UxfsfCW3N4k`#Ctqk6l!lQk+>i#bgWhm=8DLi5mv(ZDj?=Zzt5#-Sj-P^Gxd)7>K zi4~To-BF%4&5au0Nn^JufzLKOw#K3E)BwCpKHAxe4w>{)(;fI4GPyA0msJ7WhyNhd z5mBGdME;*Z4StifZ$-8PsC}M&6tX|e(FEVEf?FI$(smoP)FF@0CbJc-Bol}AD?VaK zf>yX`#xM0N(5cW7d*{X0v1U~&H^fHS?mZaa2!8HBB30a!PB?ouAGjY#>N%@!9z9## z1^I@gWTv>+f3I{K9}|lWc~%ioCxn=qysG!8->{? zSYPvDtqoRX^pp^vg+up-%^jj*YY+=k2AMzl8v&kcX*@`uS&gk|Z1y+eicM=8&VVFa zimZ$q4nm`cOT;aGFYdVSWUKBxAAYt-EiL%RDicHEu+^NCLfo5S3dQyD!EnO zuA&Ej2~CPI=+R{kLC`GO~T^Aw#i< zb7?+OP^Pj!4a283V0w-NEHf0=v@0pP5TSahKiN-#+dkK1$p;$d;=iK_Wvz zms2`^ShQjjPIZV?K}<7dzO?^J#EJ62x#5kSZ`8bcTW=%oVgOAf($#e(DI!5jV@FM~ zK^GSWwPFqjFT2Yfk`IpWQMeBsutP^#0mEEZ?&L*fJqh(*v(aytJQx{z(pJ;Z{q`i? zH;QXi`0s654>~&%va+3S95omlfxd+n12+&YVV`;&*;XNj106v#vZ=VW-9{8dpJ_yu=jJ;2zrpSh3Kl+A7p2C}eJYVC z7Ds~e2pg*l(N@E}4U8S6ELW5O6TZLrB74Tb@S-@rnhL3W(|h&m*QWL1SG+fU%aPbV z_3g+T#}NuG5l*|EuclhgACc*hg5E%QGG|KzBo$QDH`y*$>SCDfM2iT`YZJq`aNRae zIw*qd$J;tag`uQEH(awsgYzGr%*B#R4Uw^Zm=P6{NjW*h&Ja4e6xn{bt?|r-Q%^8v z=UeT>_0%VjztR01=Y3Y&+S@8yUxI~pn!@`_)Oe@sbgJrD>&BbV+X(PzovJ!1Bm(xG z=Ah^QoI6+oI_Z78z=~~okOYEfi;ZP9epE~h?yiM*jNXD>-dgMR^!pYoEGJr9Vr%la z`0p`p>Fymuk$qkTBQ9I6elQr*Ps?EDVu5dtwYX&VVaq#w zwhT}jkPx~s*BcQXva5Id>hf*6ut$9uMdIwI@=a%hVv_uIXsXuu(dkMapTZHnAmfMj zjCKLMH0-Q_(YFlO_;g5`AFytB04#A%{yBATv%56Tjn0!HmP0r^Gb}xo}dWMb6ujEh8>YMKV`9(vV?HoGhc|S zk}oS>T-2UN&!5z^+^a|OO04hGllq?!O|Vzi52*;E?XOSgDuI!&;;a1=Qk_}xRmc4! zhf{^`==!LWk9*DF|`y;U4(cD)m{AY~x9YV0rrt4?cS}p%hG5w5OjBpW;qbBHH zvJqfB(#fOHYzlm}-0Qgs8@)j;8C?_Okrn=MOPy5cYCVGyKQ5@#u_>@){Get^@!Z?p zi&T|jCNvP=d=k|9B`rL?g1v6VlV#Y?(K*L5CMLfm@*@)O^Wv0`{H@QfmR6K;z`c{w z@7NV}Lsl9zBUYFZzIH@7FY+)BU(U+?rGwZdc4np5?(-^`6FsTLHiN*Nahfb;K~%q-#gfN*%qgtZsRy#IO^4M%`xqB2?3K30PBC zosqsEG%=8=AztCX~-mGC@cDGv-J-m%Mc{>0c3km)!hFb>`Fqj&V-mckE8t#8g~--S#{2ZUokMn@V~NlhyY^%oiPQ8 zU^|h<7yL+@v(5z0ur>`#!&i042Vc_dg2a|uKWWw3y`yW)igS2QfNwEQc2+fG z61a_UOZi#<#HgGoNcFl!QKw0)ZQ})vB7|GhWs`)+a{hSXiPuiM-V;M_1wHP58SlbnD0E2YEBSXx%LQ=uJOpY{H(6T*F6G9C|r4u?RRf_Z^G`W;Z69MU9;fe?XB0UMA34(z1BBA#t z(p#iR5rKdrReA}%7$8VU9`k0_teNY3=kr--?Jwu-{r~O%+NVkJS-#?HX_pH|-0+Ml zm&&7zDdRpRG0iumF>qvg0Ab6`ZS$nK>%}n=g~{7|CvFDc0*r)bXzMjP3f84lrb*7H zRcDgeRD}n`bHfkgKoD3=dw|37{->YySz-4;L}w9ScRU>Y4ubDdO5zn7{aCHU&MB?!W#h4NiN)C_K%*xV;6WL>${Rax6?53eLu8nj$-zM;K30N8@|Dm*N`c= z*XGP zLilJGqeT#gR17Cg?9=@$TT45|fPn4^Da4#9_GJfW0$DmAUcWtBsNeQBo=jh8Q@vUL z1Y+IvW`5~Jd;;>$P{$(GStv|xv$@*2x&pYwj0#}r!RB(I%>LFff$G{J^{Wo z&N&qPGxI~fcvAlh{KRji)k{f=#i7p`+G^I;#`lT>w1euO@0M-?#|DP*0yQqMId37w zHvy)$2vfQH!7GPDW$U+?L@jvre6p&SpOI5ijN2Kt7PxtVdTEeu%-SQ?&^zRlh+YZj zZ~gpAuN-FZd{3GJcZcb3e^t}nc*n+b9^v6xl zZ8WxLz6Hw1rMDVAJW;KL>>8e9R@+ed`iZ$(VAAblNOtru(gk#8?Q-}vg6t$6`xuq~ za|`u0Ft~Tqc`dUNUSY?Vw4s(t)SBY^9zx+SsQwoyMMpXw9&|DkqtR;*n~TMJYQb zIvW3*vGK$acTwvggZg-4|I%#I8M_i`2{K|(yYP#~{vFTNCxrqfC}TFr8uU71x8jZgPf4$# zP0){MqwxjTC);Ry7A(TyoUaBlNYZ%2W84t(^~KH_a43U9nSH6d?_HUvy%2hhZ2l)A zTBD=il@xkGrvt8ByE*t4aM3N_BOUXULk9}gyk)Logymp6c$QqcpRzh8&E)vukyX9b zjwIBHIMcx4*nWS7Akp_V$WVIuuq^eK>XW|mTv9f79!u1v@EMvag(Zxi0(bO6ROBAp z4;N8m5a9js8Q+2ATWv3;8bP0O=LlgTPQ?-W>hOi119=u9m^K{lbDH>IC*!kU33>JV zeIhvY5u*a`Rp>YLXjc7RJm}azSPS%py+d@`q?_I5?YiR}-gcd79oVQSi3(EJP&iW_ z;b_fJ4cmo&;yYLR4FOD<0j4NiEzpO;4|I&k6rSPHh?+NSkdVOd+fsu30xGK6#nN`V6{!xrLBmoL zHX(Ghrf)K{tnPwf^gU4UHNLXt{R>z2niiso>h>I*)gJqTa(@s)IJR`V=n*aZK>TQ| zicZb6b)0N8vcr9e=-9;37Bn00hGGC`Y&}MV!iGc}5#-pt1LX_mxS#M-SP0gzMnh|b zd+X7tI`V1(ELi5!Mp=3|V2|x!Kl|AEFW&30<3}*c)bM~Vnk6q*z?Ct~SEih5CvF$8 zCOjFghtfZ@G9WwbHTVM|F;wu(j+7V!)UfBCU;L;?NdrdSQhfOZ*1ME7igVRMC5Fj6 z({Jui+Rcm0)oH!U1$p-U^#dP|M8)+ykuM%|R)_nl{n;KerJ&+usY*KF5$W|4{-NNf zzB~Qiw9!*|+GNdPtum4li>K`=YkR7(4Uf1J2Qof)-&x(EVGJybsDcE^f%Q+44t56# zuess$(o2iO+$KS3)$ZyIwN)Y>d(VE@)_DEB;}g28i{K<$-h5~nDAPVFBhJr-7lKGc zk(`e;3^SliIzFdDBgN+%#;f7Mrhm07NSoN+1{=A?bwu2a_|uT!JGR#I*G0fb@8B4}Y=e?hgz8)Rpog68+;^SF#^1j^-eW;{x)+*Y1@uILrVkr^^W4toEc=&)9 zDMkZVrU`4{HTZgcIEDDM?Ozyu?fM8uKrR#^WY6j-5X~l?w3_rwrui^6e!BbS!H5 zcr!8ztM|aO$g1s3mYyl_)3nu>oAD?xNp;wIy0;m!J`(AGTySxH>mz#I?7Sl0=o|cY$90?*Up(16g0^#mF7H1@vsD-P z(0~otwmkE#{Ec0%t~Qm934}H89GmPDTHHJC!PYNfvUhSW5F| zmeK?S6Ag`NJ(DvfE-K`~QN@(Y9L|B-#%4PTyZ(i8k@-{ai;PR-nz|Zt!|NhSaX0HM zn&TRJm_m~=!zbl1!rh@xu<_uXOVY;sj|2JeRPwv}kYABVqVt<{4IHmNV>-y6{!Y>5 zzHvg&HZ`LsZt&pSAu{sw$r4SsN{4du#4x%7yKmBm0v;-aV8YHv<-m=f9m`0ypm-U% zmp4ZtgVy?7aAG2M)^LZ1F80A_3=M5yWKxs&e_c`hx$a^C5L`TbiSOCT6kY9kY9CsD z*v{$b`WbFbm*~WrPh~qOHd4^kGZp)awxu@>QU58Zpm&6VVXU!ubSrjl)~_vc*C>q@ z9OMwJ%N??y-dxARlOU0-p5z*$=#qkCqcU}H?$@CfmWoA{N)3Dj15m8h)=)5&?OVSnb%d2w@rCm_;^2kL!~Dg5sIfEXvI|6`)p^U(d4Nx7Ud z^#y_E%Yqh411O)bKyd`=Yd9+oc#DgHw)*YQh_8*ovw{r5?#^%(tRUCQVT9yCN@pFq z*;N-SLi*amS+ejv)H9+xB+XBgI?DSbICv-?_2tIHDk>j_u0nS%&!olatFeY%w>=DY zeYiOtzYE0c0#8|1-gc?$c2Mi!IyY959)iIAOHaw0FDHZaNbckuci>J%mJ<0(Uv)ae zmX)rjXoeImYa$H>{5$^EF3AN1r}C2%nbO?dmN*vg8v)YM7AUXi+Muy?txPo~D1t0b zAbI_QXh}y-k-*DLmvD`!4f>p{U^{eVRe!~d%$a74Sqxd68>n@if`%oVY;dal>J~2f z1->E>np%nI&R;BlI7zrmQ8431thz6QzU5y@>8ZK@P#@j zNI^@pPXlNs8U^+mePnl>9plc`b)DC5(Z({T$O!T+W%>1`(^*UU6vImmFr_&&z-!&^ zcePl>UkN(^j<__IXTN!p4f~=dmA=8l@4{gI1nYuEYOAd57;1CVpJ;i>Wcav#=!N)y-2^i zZ+N4~e6Fp;$O@h=ss*n@Ij$_yF<(pI@yOc|=HKPl;S{~kkFoTgV^$w{l{jJ5T3-3~ zri0yDx6Zw0K1;M>_(!f5Fuzx&qrOw*}roYo{eU>O{sotrC0=HT#79g};V{QK*{+kpCGn zR`Thn)Nl(jle>?60L__zAs$e4ax#VUi|~;rs#`8m)_M2{7^A5`Qw-maS=_q_x@M?r z+l7aU3`21!aeA=q9qHgQZzgr2?1ojN8F20%S=k)S$VL=CS0;} z$8r20$$f}v9Kj1)fex%Q@2$K>sW8XKL;h~&0JBki=?I7Eg@^mCm(#(EhJ-4e@;Nx) z$`JA@viSXtV9B<$Pd;_Lg+I**)}=-kSI(9qO0;$> zo)A}?F4I~U7g#^}k#RC&s9%uc3xbg#+ivDMm6zy3(ZTPX`N)}Bc<>b9NTr@m#eKiN zN%mD3Ek06HD2ctSJG)m2SCkDKVl``XCf(CM$Z&#q5Ex8)%s1XVc7aL{b|OAAeIz3z z$LQ;5TGVU)ftbDiT*m@IF9)i0-^L41v#ZH9$)2no#${c8zK-YpQik0hS~vH%bW@I} zM?VrjT>edmsPTjXK8e?*EK45ACPDd9SWog#2qJb%3R)lCYwFE*{DcDUlvln_lsI?T z(yLN@03dF6d=ocmY+^#$r>I8*1K3RaCv%BWX;j{y~I@aGVEVAH(h@j z>_TN$l@-`Bq4v?FwV8BjyQ+?f8;LUJ_tPntT`l07(A4)Jx0oLG4TuihYKcINSKj0_q&2-pa=j*=E@1p+`4UF@wfNy-!4Bw3JZXp|26L+zveBz|D!$hzf2DA z8IO1*p{JHP_)tBGpYGLy+J9LD|4nn@KtS@4@IY!d^7IrDb~^T3O#eKsO)u9@R5R0L aj6^a5+1{1Qr|>4bT>84kI@MZE$o~O2; Date: Mon, 20 Dec 2021 23:04:11 +0000 Subject: [PATCH 174/175] docs(readme): fix assets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9aa1b5b..d7e308a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Send Veeam Backup & Replication session summary notifications to Discord, detailing session result and statistics and optionally alerting you via mention when a job finishes in a warning or failed state. -Notification Example +Notification Example ## Installing @@ -15,7 +15,7 @@ Send Veeam Backup & Replication session summary notifications to Discord, detail PS> Unblock-File C:\path\to\Installer.ps1 PS> C:\path\to\Installer.ps1 ``` - Installer Example + Installer Example * Option 2 - Manual install * Follow the [setup instructions](https://blog.tiga.tech/veeam-b-r-notifications-in-discord/). From 200699bd17fd6fef7d103ffb398a4d9e585aedac Mon Sep 17 00:00:00 2001 From: tigattack <10629864+tigattack@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:12:14 +0000 Subject: [PATCH 175/175] fix(installer): revert merge changes --- Installer.ps1 | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Installer.ps1 b/Installer.ps1 index d40240a..4f062e0 100644 --- a/Installer.ps1 +++ b/Installer.ps1 @@ -69,18 +69,19 @@ If ($mentionPreference -ne 1) { } # Pull latest version of script from GitHub -Invoke-WebRequest -Uri "https://github.com/tigattack/$project/archive/refs/heads/master.zip" ` - -OutFile "$env:TEMP\$project-master.zip" +Invoke-WebRequest -Uri ` + "https://github.com/tigattack/$project/releases/download/$latestVersion/$project-$latestVersion.zip" ` + -OutFile "$env:TEMP\$project-$latestVersion.zip" # Unblock downloaded ZIP -Unblock-File -Path "$env:TEMP\$project-master.zip" +Unblock-File -Path "$env:TEMP\$project-$latestVersion.zip" # Extract release to destination path -Expand-Archive -Path "$env:TEMP\$project-master.zip" -DestinationPath "$rootPath" +Expand-Archive -Path "$env:TEMP\$project-$latestVersion.zip" -DestinationPath "$rootPath" # Rename destination and tidy up -Rename-Item -Path "$rootPath\$project-master" -NewName "$rootPath\$project" -Remove-Item -Path "$env:TEMP\$project-master.zip" +Rename-Item -Path "$rootPath\$project-$latestVersion" -NewName "$rootPath\$project" +Remove-Item -Path "$env:TEMP\$project-$latestVersion.zip" # Get config $config = Get-Content "$rootPath\$project\config\conf.json" -Raw | ConvertFrom-Json