Vor kurzem bin ich auf das Chat Bot Projekt PoshBot gestoßen, dabei handelt es sich um einen in PowerShell verfassten Slack Bot. Da ich diese Art des operativen Vorgehens schon länger spannend finde, habe ich mich daran gesetzt durch mein eigenes Veeam Plugin for PoshBot die Funktionalität etwas zu erweitern. Denn nur durch relevante Befehle und Funktionen wird ein solches Tool vom ‚Spielzeug‘ auch zu einem echten Hilfsmittel.
Das PoshBot Projekt macht es einem durch seine gute Dokumentation wirklich sehr einfach einen Einstieg zu finden. Innerhalb von weniger Stunden war ich in der Lage die ChatBot App in meinen Slack Channel einzubinden und den ersten Befehl meines eigenen Plugins aufzurufen.
Mittlerweile habe ich noch zwei weitere Kommandos hinzugefügt und es folgen hoffentlich bald noch viele weitere.
Wie Starte ich PoshBot
Ich habe meinen Einstieg in das Thema durch den tollen Artikel von Warren F. gefunden. In diesem Artikel werden unter anderem verschiedene Wege aufgeführt wie man den PoshBot Prozess startet (ist auch in der ReadTheDocs Doku des Projekts zu finden). Ich habe mir der Einfachheit halber eine PowerShell Funktion geschrieben um den Chat Bot in meiner Konfiguration nach Bedarf zu starten. In der Produktion ist sicher die Implementation als Dienst sinnvoller.
Allgemeine Voraussetzungen:
- PowerShell 5.x
- PoshBot Modul
Veeam Plugin Voraussetzungen:
- Veeam PowerShell Snapin
- User hat Vollzugriff im Veeam Backup & Replication Server
- Egal ob manuell gestartet oder als Dienst ausgeführt
- Nicht zwingend erforderlich aber ratsam wäre es, PoshBot direkt auf dem Veeam Backup & Replication Server auszuführen
- dadurch erspart man sich bei den Kommandos den optionalen Parameter –brserver
function Start-VeeamPoshBot ($Token) { Import-Module -Name PoshBot $botParams = @{ Name = 'veeambot' BotAdmins = @('vmarkus_k') CommandPrefix = '!' LogLevel = 'Verbose' BackendConfiguration = @{ Name = 'SlackBackend' Token = $Token } AlternateCommandPrefixes = 'bender', 'hal' } $myBotConfig = New-PoshBotConfiguration @botParams Start-PoshBot -Configuration $myBotConfig }
Input Parameter für die Funktion ist dann nur noch der Slack API Token für den Bot (vorausgesetzt das PoshBot Modul ist bereits Installiert).
Start-VeeamPoshBot -Token '<Your API Token>'
Mein Veeam Plugin for PoshBot
In dem Artikel von Warren F wird auch auf die Entwicklung von Plugin für PoshBot eingegangen. Ich fand jedoch den Abschnitt Developing Plugins der Projekt Dokumentation etwas übersichtlicher.
Was mich nun wirklich begeistert hat an dem PoshBot Konzept ist, dass man seine bestehenden PowerShell Funktionen mit wenigen Anpassungen zu einem PoshBot Plugin umwandeln kann. Genau das habe ich auch mit meinem PRTG Veeam Advanced Sensor gemacht indem ich dieses Skript in zwei PowerShell Funktionen aufgeteilt habe: Get-VeeamRepositories, Get-VeeamJobSessions und Get-VeeamJobs. Sehr schnell war ich aber mit dem Detailgrad der Ausgabe von Get-VeeamJobSessions unzufrieden und habe mit Hilfe des PowerShell-VeeamAllStats Moduls von Timothy Dewin meine Funktion erweitert.
Get-VeeamRepositories
Die Zeilen 13-16 und 94-105 sind die einzigen PoshBot spezifischen Ergänzungen die aus einer normalen PowerShell Funktion ein PoshBot Kommando mit Aliasen, Rechten und übersichtlicher Ausgabe macht
function Get-VeeamRepositories { <# .SYNOPSIS Get Veeam Repositories .EXAMPLE !Get-VeeamRepositories .EXAMPLE !Repos .EXAMPLE !VeeamRepositories #> [PoshBot.BotCommand( Aliases = ('Repos', 'VeeamRepositories'), Permissions = 'read' )] [cmdletbinding()] param( [Parameter(Position=0, Mandatory=$false)] [string] $BRHost = "localhost" ) #region: Functions Function Get-vPCRepoInfo { [CmdletBinding()] param ( [Parameter(Position=0, ValueFromPipeline=$true)] [PSObject[]]$Repository ) Begin { $outputAry = @() Function New-RepoObject {param($name, $repohost, $path, $free, $total) $repoObj = New-Object -TypeName PSObject -Property @{ Target = $name RepoHost = $repohost Storepath = $path StorageFree = [Math]::Round([Decimal]$free/1GB,2) StorageTotal = [Math]::Round([Decimal]$total/1GB,2) FreePercentage = [Math]::Round(($free/$total)*100) } Return $repoObj | Select-Object Target, RepoHost, Storepath, StorageFree, StorageTotal, FreePercentage } } Process { Foreach ($r in $Repository) { # Refresh Repository Size Info try { [Veeam.Backup.Core.CBackupRepositoryEx]::SyncSpaceInfoToDb($r, $true) } catch { Write-Debug "SyncSpaceInfoToDb Failed" } If ($r.HostId -eq "00000000-0000-0000-0000-000000000000") { $HostName = "" } Else { $HostName = $($r.GetHost()).Name.ToLower() } $outputObj = New-RepoObject $r.Name $Hostname $r.Path $r.info.CachedFreeSpace $r.Info.CachedTotalSpace } $outputAry += $outputObj } End { $outputAry } } #endregion #region: Start BRHost Connection Connect-VBRServer -Server $BRHost #endregion #region: Collect and filter Repos [Array]$repoList = Get-VBRBackupRepository | Where-Object {$_.Type -ne "SanSnapshotOnly"} [Array]$scaleouts = Get-VBRBackupRepository -scaleout if ($scaleouts) { foreach ($scaleout in $scaleouts) { $extents = Get-VBRRepositoryExtent -Repository $scaleout foreach ($ex in $extents) { $repoList = $repoList + $ex.repository } } } #endregion #region: Build Report $RepoReport = $repoList | Get-vPCRepoInfo | Select-Object @{Name="Repository Name"; Expression = {$_.Target}}, @{Name="Host"; Expression = {$_.RepoHost}}, @{Name="Path"; Expression = {$_.Storepath}}, @{Name="Free (GB)"; Expression = {$_.StorageFree}}, @{Name="Total (GB)"; Expression = {$_.StorageTotal}}, @{Name="Free (%)"; Expression = {$_.FreePercentage}} forEach ($Repo in $RepoReport) { $r = [pscustomobject]@{ Name = $($Repo.'Repository Name') 'Free (%)' = $($Repo.'Free (%)') 'Free (GB)' = $($Repo.'Free (GB)') 'Total (GB)' = $($Repo.'Total (GB)') 'Host' = $($Repo.'Host') 'Path' = $($Repo.'Path') } New-PoshBotCardResponse -Title "$($Repo.'Repository Name'):" -Text ($r | Format-List -Property * | Out-String) } #endregion }
Get-VeeamJobSessions
function Get-VeeamJobSessions { <# .SYNOPSIS Get last Veeam Job Sessions .EXAMPLE !Get-VeeamJobSessions .EXAMPLE !Get-VeeamJobSessions --brhost <your BR Host> .EXAMPLE !JobSessions .EXAMPLE !VeeamJobSessions #> [PoshBot.BotCommand( Aliases = ('JobSessions', 'VeeamJobSessions'), Permissions = 'read' )] [cmdletbinding()] param( [Parameter(Position=0, Mandatory=$false)] [string] $BRHost = "localhost" ) #region: Start BRHost Connection Connect-VBRServer -Server $BRHost #endregion #region: Collect Sessions $Stats = (Get-VeeamAllStat).jobsessions #endregion #region: Build Report forEach ($Stat in $Stats) { $r = [pscustomobject]@{ JobName = $Stat.name JobType = $Stat.type HasRan = $Stat.hasRan JobStatus = $Stat.status CreationTime = $Stat.creationTime EndTime = $Stat.endTime VMsTotal = $Stat.vmstotal VMsSuccess = $Stat.vmssuccess VMsWarning = $Stat.vmswarning VMsError = $Stat.vmserror } New-PoshBotCardResponse -Title "Job '$($Stat.name)' Stats:" -Text ($r | Format-List -Property * | Out-String) } #endregion }
In meinem Veeam Plugin for PoshBot verwendete Klassen und Funktionen aus dem genialen PowerShell-VeeamAllStats Modul von Timothy Dewin:
class VeeamAllStatJobSessionVM { $name $status; $starttime; $endtime; $size; $read; $transferred; $duration; $details; VeeamAllStatJobSessionVM() {} VeeamAllStatJobSessionVM($name,$status,$starttime,$endtime,$size,$read,$transferred,$duration,$details) { $this.name = $name $this.status = $status; $this.starttime = $starttime; $this.endtime = $endtime; $this.size = $size; $this.read = $read; $this.transferred = $transferred; $this.duration = $duration; $this.details = $details } } class VeeamAllStatJobSession { $name = "<no name>" $type = "<no type>"; $description = ""; $status = "<not run>"; [System.DateTime]$creationTime='1970-01-01 00:00:00'; [System.DateTime]$endTime='1970-01-01 00:00:00'; [System.TimeSpan]$duration $processedObjects=0; $totalObjects=0 $totalSize=0 $backupSize=0 $dataRead=0 $transferredSize=0 $dedupe=0 $compression=0 $details="" $vmstotal=0 $vmssuccess=0 $vmswarning=0 $vmserror=0 $allerrors=@{} [bool]$hasRan=$true [VeeamAllStatJobSessionVM[]]$vmsessions= @() VeeamAllStatJobSession() { } VeeamAllStatJobSession($name,$type,$description) { $this.name = $name $this.type = $type; $this.description =$description; } VeeamAllStatJobSession($name,$type,$description,$status,$creationTime,$endtime,$processedObjects,$totalObjects,$totalSize,$backupSize,$dataRead,$transferredSize,$dedupe,$compression,$details) { $this.name = $name $this.type = $type; $this.description =$description; $this.status = $status; $this.creationTime=$creationTime; $this.endTime=$endTime; $this.processedObjects=$processedObjects; $this.totalObjects=$totalObjects $this.totalSize=$totalSize $this.backupSize=$backupSize $this.dataRead=$dataRead $this.transferredSize=$transferredSize $this.dedupe=$dedupe $this.compression=$compression $this.details=$details $this.duration=$endTime-$creationTime } } class VeeamAllStatJobMain { $versionVeeam $server $serverString [VeeamAllStatJobSession[]]$jobsessions VeeamAllStatJobMain () { $this.jobsessions = @() } } function Get-VeeamAllStatJobSessionVMs { param( $session, [VeeamAllStatJobSession]$statjobsession ) $tasks = $session.GetTaskSessions() foreach($task in $tasks) { $s = $task.status $vm = [VeeamAllStatJobSessionVM]::new( $task.Name, $s, $task.Progress.StartTime, $task.Progress.StopTime, $task.Progress.ProcessedSize, $task.Progress.ReadSize, $task.Progress.TransferedSize, $task.Progress.Duration, $task.GetDetails()) if ($s -ieq "success") { $statjobsession.vmssuccess += 1 } elseif ($s -ieq "warning" -or $s -ieq "pending" -or $s -ieq "none") { $statjobsession.vmswarning +=1 } else { $statjobsession.vmserror += 1 } if ($vm.details -ne "") { $statjobsession.allerrors[$task.Name]=$vm.details } $statjobsession.vmsessions += $vm $statjobsession.vmstotal+=1 } } function Get-VeeamAllStatJobSession { param( $job, $session ) $statjob = [VeeamAllStatJobSession]::new( $job.Name, $session.JobType, $job.Description, $session.Result, $session.CreationTime, $session.EndTime, $session.Progress.ProcessedObjects, $session.Progress.TotalObjects, $session.Progress.TotalSize, $session.BackupStats.BackupSize, $session.Progress.ReadSize, $Session.Progress.TransferedSize, $session.BackupStats.GetDedupeX(), $session.BackupStats.GetCompressX(), $session.GetDetails() ) Get-VeeamAllStatJobSessionVMs -session $session -statjobsession $statjob if ($session.Result -eq "None" -and $session.JobType -eq "BackupSync") { if($session.State -eq "Idle" -and $statjob.vmserror -eq 0 -and $statjob.vmswarning -eq 0 -and $statjob.allerrors.count -eq 0 -and $statjob.details -eq "" -and $session.EndTime -gt $session.CreationTime ) { if ($session.Progress.Percents -eq 100) { $statjob.Status="Success" } } } return $statjob } function Get-VeeamAllStatJobSessions { param( [VeeamAllStatJobMain]$JobMain ) $allsessions = Get-VBRBackupSession $allorderdedsess = $allsessions | Sort-Object -Property CreationTimeUTC -Descending $jobs = get-vbrjob foreach ($Job in $Jobs) { $lastsession = $allorderdedsess | ? { $_.jobid -eq $Job.id } | select -First 1 if ($lastsession -ne $null) { $JobMain.jobsessions += Get-VeeamAllStatJobSession -job $job -session $lastsession } else { $s = [VeeamAllStatJobSession]::new($job.Name,$job.type,$job.description) $s.hasRan = $false $JobMain.jobsessions += $s } } } function Get-VeeamAllStatServerVersion { param( [VeeamAllStatJobMain]$JobMain ) $versionstring = "Unknown Version" $pssversion = (Get-PSSnapin VeeamPSSnapin -ErrorAction SilentlyContinue) if ($pssversion -ne $null) { $versionstring = ("{0}.{1}" -f $pssversion.Version.Major,$pssversion.Version.Minor) } $corePath = Get-ItemProperty -Path "HKLM:\Software\Veeam\Veeam Backup and Replication\" -Name "CorePath" -ErrorAction SilentlyContinue if ($corePath -ne $null) { $depDLLPath = Join-Path -Path $corePath.CorePath -ChildPath "Packages\VeeamDeploymentDll.dll" -Resolve -ErrorAction SilentlyContinue if ($depDLLPath -ne $null -and (Test-Path -Path $depDLLPath)) { $file = Get-Item -Path $depDLLPath -ErrorAction SilentlyContinue if ($file -ne $null) { $versionstring = $file.VersionInfo.ProductVersion } } } $servsession = Get-VBRServerSession $JobMain.versionVeeam = $versionstring $JobMain.server = $servsession.server $JobMain.serverString = ("Server {0} : Veeam Backup & Replication {1}" -f $servsession.server,$versionstring) } function Get-VeeamAllStat { $report = [VeeamAllStatJobMain]::new() Get-VeeamAllStatServerVersion -JobMain $report Get-VeeamAllStatJobSessions -JobMain $report return $report } function Get-HumanDataSize { param([double]$numc) $num = $numc+0 $trailing= "","K","M","G","T","P","E" $i=0 while($num -gt 1024 -and $i -lt 6) { $num= $num/1024 $i++ } return ("{0:f1} {1}B" -f $num,$trailing[$i]) } function Get-HumanDate { param([DateTime]$t) return $t.toString("yyyy-MM-dd HH:mm:ss") } function Get-HumanDuration { param([System.TimeSpan]$d) return ("{0:D2}:{1:D2}:{2:D2}" -f ($d.Hours+($d.Days*24)),$d.Minutes,$d.Seconds) }
Detailansicht eines Jobs:
Der Aufruf mit dem alternativen PoshBot Kommando und dem Alias für Get-VeeamSessions aus dem Veeam Plugin for PoshBot sieht in meinem Falls so aus:
Get-VeeamJobs
function Get-VeeamJobs { <# .SYNOPSIS Get Veeam Jobs .EXAMPLE !Get-VeeamJobs .EXAMPLE !Get-VeeamJobs --brhost <your BR Host> .EXAMPLE !Jobs .EXAMPLE !VeeamJobs #> [PoshBot.BotCommand( Aliases = ('Jobs', 'VeeamJobs'), Permissions = 'read' )] [cmdletbinding()] param( [Parameter(Position=0, Mandatory=$false)] [string] $BRHost = "localhost" ) #region: Start BRHost Connection Connect-VBRServer -Server $BRHost #endregion #region: Collect Jobs [Array] $Jobs = Get-VBRJob #endregion #region: Build Report $report = @() forEach ($Job in $Jobs) { $object = [pscustomobject]@{ Name = $Job.Name JobType = $Job.JobType } $report += $object } New-PoshBotCardResponse -Title "Veeam Jobs:" -Text ($report | Format-List -Property * | Out-String) #endregion }
Start-VeeamQuickBackup
function Start-VeeamQuickBackup { <# .SYNOPSIS STart Veeam Quick Backup .EXAMPLE !Start-VeeamQuickBackup --ViEntity <VM Name> .EXAMPLE !Start-VeeamQuickBackup --ViEntity <VM Name> --brhost <your BR Host> .EXAMPLE !VuickBackup --ViEntity <VM Name> .EXAMPLE !VeeamQuickBackup --ViEntity <VM Name> #> [PoshBot.BotCommand( Aliases = ('QuickBackup', 'VeeamQuickBackup'), Permissions = 'write' )] [CmdletBinding()] param( [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullorEmpty()] [String]$ViEntity, [Parameter(Mandatory=$false, Position=1)] [ValidateNotNullorEmpty()] [string] $BRHost = "localhost" ) #region: Load PSSnapIn if ( (Get-PSSnapin -Name VeeamPSSnapin -ErrorAction SilentlyContinue) -eq $null ) { Add-PSsnapin VeeamPSSnapin } #endregion #region: Start BRHost Connection Connect-VBRServer -Server $BRHost #endregion if ($VeeamVm = Find-VBRViEntity -Name $ViEntity) { $Result = Start-VBRQuickBackup -VM $VeeamVm -Wait if ($Result) { #region: Build Report $report = @() $object = [pscustomobject]@{ JobName = $Result.JobName Result = $Result.Result CreationTime = $Result.CreationTime EndTime = $Result.EndTime } $report += $object New-PoshBotCardResponse -Title "Veeam Quickbackup Result:" -Text ($report | Format-List -Property * | Out-String) #endregion } if ($Result.Result -eq "Failed") { Throw "Quick Backup Failed. See Veeam B&R Console for more details!" } } else { Throw "No ViEntity '$ViEntity' found!" } }
Install Veeam Plugin for PoshBot
Um mein Veeam Plugin for PoshBot ganz unkompliziert zur Verfügung zu stellen habe ich es mit dem entsprechenden Tag in der PowerShell Gallery veröffentlicht. Alle verfügbaren Plugins lassen sich mit dem Kommando !find-plugin ganz einfach auflisten und mit !install-plugin auch direkt installieren.
Nach der Installation steht auch die Hilfe zu dem Plugin zur Verfügung. Besonders interessant sind hierbei auch die Aliase für die Kommandos, damit ist die Eingabe (besonders am Smartphone) doch etwas flotter.
Ebenso kann die Hilfe für die einzelnen Kommandos des Plugins angezeigt werden und auch die notwendigen Rechte geprüft werden, dazu aber später mehr.
Mit dem Slack Befehl !help poshbot.veeam:Get-VeeamJobSessions –detailed kann die vollständige Hilfe für das Kommando, inklusive Beispielen aufgerufen werden.
Die Ausführung der Kommandos des Plugins ist dank Access Control nochmal eine andere Sache:
Nachdem der Slack User der passenden Gruppe zugeordnet wurde, klappt es aber auch mit den Kommandos:
Die Gruppe habe ich vorab mit den entsprechenden Befehlen vorbereitet und die Rechte für das Veeam Plugin for PoshBot vergeben:
!New-Group veeamadmins !New-Role veeamadmins !Add-GroupRole veeamadmins veeamadmins !Add-RolePermission veeamadmins poshbot.veeam:read !Add-RolePermission veeamadmins poshbot.veeam:write
Weiterentwicklung Veeam Plugin for PoshBot
Wie so viele meiner Projekte ist auch dieses im Status „Work in Progress“ und ich würde mich über etwas Input freuen welche Funktionen praktisch oder hilfreich wären.
Ich bin dankbar über jeden Kommentar zu diesem Beitrag bzw. Issue oder Enhancement Request in dem dazugehörigen GitHub Projekt.
No Responses