From 3985a88f3afd9ff2e3eef5251a6678381f130790 Mon Sep 17 00:00:00 2001 From: lordmilko Date: Sun, 19 Nov 2017 22:14:14 +1100 Subject: [PATCH] -Implemented BackupConfigDatabase, ClearSystemCache, LoadConfigFiles, RestartProbe and RestartCore methods -Implemented Backup-PrtgConfig, Clear-PrtgCache, Load-PrtgConfigFile, Restart-Probe and Restart-PrtgCore cmdlets For #5 --- .../ActionTests/AdminToolTests.cs | 57 +++++ .../PowerShell/Backup-PrtgConfig.Tests.ps1 | 26 ++ .../PowerShell/Clear-PrtgCache.Tests.ps1 | 14 ++ .../PowerShell/Load-PrtgConfigFile.Tests.ps1 | 11 + .../PowerShell/Restart-Probe.Tests.ps1 | 31 +++ .../PowerShell/Restart-PrtgCore.Tests.ps1 | 19 ++ .../PrtgAPI.Tests.IntegrationTests.csproj | 6 + .../Support/Impersonator.cs | 10 +- .../ObjectTests/CSharp/AdminToolTests.cs | 80 ++++++ .../PowerShell/Backup-PrtgConfig.Tests.ps1 | 9 + .../PowerShell/Clear-PrtgCache.Tests.ps1 | 15 ++ .../PowerShell/Load-PrtgConfigFile.Tests.ps1 | 15 ++ .../PowerShell/New-SearchFilter.Tests.ps1 | 5 - .../PowerShell/Restart-Probe.Tests.ps1 | 60 +++++ .../PowerShell/Restart-PrtgCore.Tests.ps1 | 23 ++ .../PowerShell/Support/UnitTest.ps1 | 34 +++ .../TestResponses/AddressValidatorResponse.cs | 46 +++- .../TestResponses/MultiTypeResponse.cs | 8 + .../TestResponses/RestartProbeResponse.cs | 89 +++++++ .../TestResponses/RestartPrtgCoreResponse.cs | 51 ++++ PrtgAPI/Enums/ConfigFileType.cs | 18 ++ PrtgAPI/Enums/Functions/CommandFunction.cs | 23 +- PrtgAPI/Enums/SystemCacheType.cs | 18 ++ .../RestartProbeParameters.cs | 11 + PrtgAPI/PowerShell/Base/PrtgCmdlet.cs | 11 + .../ObjectManipulation/BackupPrtgConfig.cs | 39 +++ .../ObjectManipulation/ClearPrtgCache.cs | 82 ++++++ .../ObjectManipulation/LoadPrtgConfigFile.cs | 59 +++++ .../ObjectManipulation/RestartProbe.cs | 234 ++++++++++++++++++ .../ObjectManipulation/RestartPrtgCore.cs | 189 ++++++++++++++ PrtgAPI/PowerShell/Resources/PrtgAPI.psm1 | 3 + PrtgAPI/PrtgClient.cs | 111 +++++++++ 32 files changed, 1397 insertions(+), 10 deletions(-) create mode 100644 PrtgAPI.Tests.IntegrationTests/ActionTests/AdminToolTests.cs create mode 100644 PrtgAPI.Tests.IntegrationTests/PowerShell/Backup-PrtgConfig.Tests.ps1 create mode 100644 PrtgAPI.Tests.IntegrationTests/PowerShell/Clear-PrtgCache.Tests.ps1 create mode 100644 PrtgAPI.Tests.IntegrationTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 create mode 100644 PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-Probe.Tests.ps1 create mode 100644 PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-PrtgCore.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/CSharp/AdminToolTests.cs create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Backup-PrtgConfig.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Clear-PrtgCache.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-Probe.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-PrtgCore.Tests.ps1 create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartProbeResponse.cs create mode 100644 PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartPrtgCoreResponse.cs create mode 100644 PrtgAPI/Enums/ConfigFileType.cs create mode 100644 PrtgAPI/Enums/SystemCacheType.cs create mode 100644 PrtgAPI/Parameters/ObjectManipulation/RestartProbeParameters.cs create mode 100644 PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/BackupPrtgConfig.cs create mode 100644 PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/ClearPrtgCache.cs create mode 100644 PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/LoadPrtgConfigFile.cs create mode 100644 PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartProbe.cs create mode 100644 PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartPrtgCore.cs diff --git a/PrtgAPI.Tests.IntegrationTests/ActionTests/AdminToolTests.cs b/PrtgAPI.Tests.IntegrationTests/ActionTests/AdminToolTests.cs new file mode 100644 index 00000000..de596b0d --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/ActionTests/AdminToolTests.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace PrtgAPI.Tests.IntegrationTests.ActionTests +{ + [TestClass] + public class AdminToolTests : BasePrtgClientTest + { + private static string PrtgBackups => $"\\\\{Settings.Server}\\c$\\ProgramData\\Paessler\\PRTG Network Monitor\\Configuration Auto-Backups"; + + [TestMethod] + public void Action_BackupConfig_SuccessfullyBacksUpConfig() + { + var originalFiles = GetBackupFiles(); + + client.BackupConfigDatabase(); + + ValidateBackupCreated(originalFiles); + } + + [TestMethod] + public async Task Action_BackupConfig_SuccessfullyBacksUpConfigAsync() + { + var originalFiles = GetBackupFiles(); + + await client.BackupConfigDatabaseAsync(); + + ValidateBackupCreated(originalFiles); + } + + public static List GetBackupFiles() => Impersonator.ExecuteAction(new DirectoryInfo(PrtgBackups).GetFiles).ToList(); + + public static void RemoveBackupFile(string fileName) => Impersonator.ExecuteAction(() => File.Delete(fileName)); + + private void ValidateBackupCreated(List originalFiles) + { + Logger.LogTest("Pausing for 10 seconds while backup is created"); + Thread.Sleep(10000); + + var newFiles = GetBackupFiles(); + + Assert2.AreEqual(originalFiles.Count + 1, newFiles.Count, "New backup file was not created"); + + var diff = newFiles.Select(f => f.FullName).Except(originalFiles.Select(fn => fn.FullName)).ToList(); + + Assert2.AreEqual(1, diff.Count, "Backup file was not successfully created"); + + var firstFile = diff.First(); + + RemoveBackupFile(firstFile); + } + } +} diff --git a/PrtgAPI.Tests.IntegrationTests/PowerShell/Backup-PrtgConfig.Tests.ps1 b/PrtgAPI.Tests.IntegrationTests/PowerShell/Backup-PrtgConfig.Tests.ps1 new file mode 100644 index 00000000..981a8191 --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/PowerShell/Backup-PrtgConfig.Tests.ps1 @@ -0,0 +1,26 @@ +. $PSScriptRoot\Support\IntegrationTestSafe.ps1 + +Describe "Backup-PrtgConfig_IT" { + It "can execute" { + $originalFiles = [PrtgAPI.Tests.IntegrationTests.ActionTests.AdminToolTests]::GetBackupFiles() | select -ExpandProperty FullName + + $originalFiles.Count | Should BeGreaterThan 0 + + Backup-PrtgConfig + + LogTest "Pausing for 10 seconds while backup is created" + Sleep 10 + + $newFiles = [PrtgAPI.Tests.IntegrationTests.ActionTests.AdminToolTests]::GetBackupFiles() | select -ExpandProperty FullName + + $newFiles.Count | Should Be ($originalFiles.Count + 1) + + $diff = @($newFiles | where { $originalFiles -notcontains $_ }) + + $diff.Count | Should Be 1 + + $firstFile = $diff | select -First 1 + + [PrtgAPI.Tests.IntegrationTests.ActionTests.AdminToolTests]::RemoveBackupFile($firstFile) + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.IntegrationTests/PowerShell/Clear-PrtgCache.Tests.ps1 b/PrtgAPI.Tests.IntegrationTests/PowerShell/Clear-PrtgCache.Tests.ps1 new file mode 100644 index 00000000..6611e412 --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/PowerShell/Clear-PrtgCache.Tests.ps1 @@ -0,0 +1,14 @@ +. $PSScriptRoot\Support\IntegrationTest.ps1 + +Describe "Clear-PrtgCache_IT" { + It "clears general caches" { + Clear-PrtgCache General + } + + It "clears graph data" { + Clear-PrtgCache GraphData -Force + + # Wait on the service restarting + Restart-PrtgCore -Force -Wait + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.IntegrationTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 b/PrtgAPI.Tests.IntegrationTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 new file mode 100644 index 00000000..6d34732e --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 @@ -0,0 +1,11 @@ +. $PSScriptRoot\Support\IntegrationTest.ps1 + +Describe "Load-PrtgConfigFile_IT" { + It "loads general files" { + Load-PrtgConfigFile General + } + + It "loads sensor lookups" { + Load-PrtgConfigFile Lookups + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-Probe.Tests.ps1 b/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-Probe.Tests.ps1 new file mode 100644 index 00000000..0720e707 --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-Probe.Tests.ps1 @@ -0,0 +1,31 @@ +. $PSScriptRoot\Support\IntegrationTest.ps1 + +Describe "Restart-Probe_IT" { + It "waits for all probes to restart" { + Restart-Probe + + $probes = Get-Probe + + foreach($probe in $probes) + { + $probe.Condition | Should Be Connected + } + } + + It "waits for a probe to restart" { + $probe = Get-Probe -Id (Settings Probe) + + $probe | Restart-Probe + + $newProbe = Get-Probe -Id (Settings Probe) + + $probe.Condition | Should Be Connected + } + + It "times out restarting a probe" { + { Restart-Probe -Timeout 1 } | Should Throw "Timed out waiting for 1 probe to restart" + + # Wait for the server to come back online + Restart-Probe -Wait + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-PrtgCore.Tests.ps1 b/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-PrtgCore.Tests.ps1 new file mode 100644 index 00000000..fc60a28f --- /dev/null +++ b/PrtgAPI.Tests.IntegrationTests/PowerShell/Restart-PrtgCore.Tests.ps1 @@ -0,0 +1,19 @@ +. $PSScriptRoot\Support\IntegrationTest.ps1 + +Describe "Restart-PrtgCore_IT" { + + It "waits for PRTG to restart" { + Restart-PrtgCore + + $sensor = Get-Sensor -Id (Settings UpSensor) + + $sensor.Id | Should Be (Settings UpSensor) + } + + It "times out restarting PRTG" { + { Restart-PrtgCore -Timeout 1 } | Should Throw "Timed out waiting for PRTG Core Service to restart" + + # Wait for the server to come back online + Restart-PrtgCore -Wait + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.IntegrationTests/PrtgAPI.Tests.IntegrationTests.csproj b/PrtgAPI.Tests.IntegrationTests/PrtgAPI.Tests.IntegrationTests.csproj index 46837d7c..5493488d 100644 --- a/PrtgAPI.Tests.IntegrationTests/PrtgAPI.Tests.IntegrationTests.csproj +++ b/PrtgAPI.Tests.IntegrationTests/PrtgAPI.Tests.IntegrationTests.csproj @@ -57,6 +57,7 @@ + @@ -96,6 +97,8 @@ + + @@ -111,12 +114,15 @@ + + + diff --git a/PrtgAPI.Tests.IntegrationTests/Support/Impersonator.cs b/PrtgAPI.Tests.IntegrationTests/Support/Impersonator.cs index af190c0a..8d1c9fb5 100644 --- a/PrtgAPI.Tests.IntegrationTests/Support/Impersonator.cs +++ b/PrtgAPI.Tests.IntegrationTests/Support/Impersonator.cs @@ -13,7 +13,7 @@ class Impersonator : IDisposable { [DllImport("advapi32.dll", SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool LogonUser( + static extern bool LogonUser( [MarshalAs(UnmanagedType.LPStr)] string pszUserName, [MarshalAs(UnmanagedType.LPStr)] string pszDomain, [MarshalAs(UnmanagedType.LPStr)] string pszPassword, @@ -74,5 +74,13 @@ public static void ExecuteAction(Action action) action(); } } + + public static T ExecuteAction(Func action) + { + using (var impersonator = new Impersonator(Settings.Server, Settings.WindowsUserName, Settings.WindowsPassword)) + { + return action(); + } + } } } diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/CSharp/AdminToolTests.cs b/PrtgAPI.Tests.UnitTests/ObjectTests/CSharp/AdminToolTests.cs new file mode 100644 index 00000000..e3b82418 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/CSharp/AdminToolTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using PrtgAPI.Tests.UnitTests.ObjectTests.TestResponses; + +namespace PrtgAPI.Tests.UnitTests.ObjectTests.CSharp +{ + [TestClass] + public class AdminToolTests : BaseTest + { + PrtgClient client = Initialize_Client(new BasicResponse(string.Empty)); + + [TestMethod] + public void AdminTool_BackupConfig_CanExecute() + { + client.BackupConfigDatabase(); + } + + [TestMethod] + public async Task AdminTool_BackupConfig_CanExecuteAsync() + { + await client.BackupConfigDatabaseAsync(); + } + + [TestMethod] + public void AdminTool_ClearCache_CanExecute() + { + client.ClearSystemCache(SystemCacheType.General); + client.ClearSystemCache(SystemCacheType.GraphData); + } + + [TestMethod] + public async Task AdminTool_ClearCache_CanExecuteAsync() + { + await client.ClearSystemCacheAsync(SystemCacheType.General); + await client.ClearSystemCacheAsync(SystemCacheType.GraphData); + } + + [TestMethod] + public void AdminTool_LoadConfigFiles_CanExecute() + { + client.LoadConfigFiles(ConfigFileType.General); + client.LoadConfigFiles(ConfigFileType.Lookups); + } + + [TestMethod] + public async Task AdminTool_LoadConfigFiles_CanExecuteAsync() + { + await client.LoadConfigFilesAsync(ConfigFileType.General); + await client.LoadConfigFilesAsync(ConfigFileType.Lookups); + } + + [TestMethod] + public void AdminTool_RestartCore_CanExecute() + { + client.RestartCore(); + } + + [TestMethod] + public async Task AdminTool_RestartCore_CanExecuteAsync() + { + await client.RestartCoreAsync(); + } + + [TestMethod] + public void AdminTool_RestartProbe_CanExecute() + { + client.RestartProbe(1001); + } + + [TestMethod] + public async Task AdminTool_RestartProbe_CanExecuteAsync() + { + await client.RestartProbeAsync(1001); + } + } +} diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Backup-PrtgConfig.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Backup-PrtgConfig.Tests.ps1 new file mode 100644 index 00000000..c5464220 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Backup-PrtgConfig.Tests.ps1 @@ -0,0 +1,9 @@ +. $PSScriptRoot\Support\Standalone.ps1 + +Describe "Backup-PrtgConfig" -Tag @("PowerShell", "UnitTest") { + It "can execute" { + SetAddressValidatorResponse "api/savenow.htm?" $true + + Backup-PrtgConfig + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Clear-PrtgCache.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Clear-PrtgCache.Tests.ps1 new file mode 100644 index 00000000..9c5af607 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Clear-PrtgCache.Tests.ps1 @@ -0,0 +1,15 @@ +. $PSScriptRoot\Support\Standalone.ps1 + +Describe "Clear-PrtgCache" -Tag @("PowerShell", "UnitTest") { + It "clears general caches" { + SetAddressValidatorResponse "api/clearcache.htm?" $true + + Clear-PrtgCache General + } + + It "clears the graph cache" { + SetAddressValidatorResponse "api/recalccache.htm?" $true + + Clear-PrtgCache GraphData -Force + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 new file mode 100644 index 00000000..41dc522d --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Load-PrtgConfigFile.Tests.ps1 @@ -0,0 +1,15 @@ +. $PSScriptRoot\Support\Standalone.ps1 + +Describe "Load-PrtgConfigFile" -Tag @("PowerShell", "UnitTest") { + It "loads general files" { + SetAddressValidatorResponse "api/reloadfilelists.htm?" $true + + Load-PrtgConfigFile General + } + + It "loads sensor lookups" { + SetAddressValidatorResponse "api/loadlookups.htm?" $true + + Load-PrtgConfigFile Lookups + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/New-SearchFilter.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/New-SearchFilter.Tests.ps1 index c57ecf6e..bd647b34 100644 --- a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/New-SearchFilter.Tests.ps1 +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/New-SearchFilter.Tests.ps1 @@ -1,10 +1,5 @@ . $PSScriptRoot\Support\Standalone.ps1 -function SetAddressValidatorResponse($str) -{ - SetResponseAndClientWithArguments "AddressValidatorResponse" $str -} - Describe "New-SearchFilter" { Context "Enum Transformation" { diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-Probe.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-Probe.Tests.ps1 new file mode 100644 index 00000000..724f6259 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-Probe.Tests.ps1 @@ -0,0 +1,60 @@ +. $PSScriptRoot\Support\Standalone.ps1 + +Describe "Restart-Probe" -Tag @("PowerShell", "UnitTest") { + It "restarts all probes" { + SetAddressValidatorResponse @( + "api/table.xml?content=probenode&count=0&filter_parentid=0&" + "api/restartprobes.htm?" + ) + + Restart-Probe -Wait:$false -Force + } + + It "restarts specific probes" { + $probes = Run Probe { + + $obj1 = GetItem + $obj2 = GetItem + $obj2.ObjId = 2 + + WithItems ($obj1, $obj2) { + Get-Probe + } + } + + $probes.Count | Should Be 2 + + SetAddressValidatorResponse @( + "api/restartprobes.htm?id=1&" + "api/restartprobes.htm?id=2&" + ) + + $probes | Restart-Probe -Wait:$false -Force + } + + It "waits for all probes to restart" { + SetResponseAndClient "RestartProbeResponse" + + Restart-Probe -Force + } + + It "waits for specified probes to restart" { + SetResponseAndClient "RestartProbeResponse" + + $probes = Get-Probe + + $probes | Restart-Probe -Force + } + + It "times out waiting for all probes to restart" { + SetResponseAndClient "RestartProbeResponse" + + { Restart-Probe -Timeout 1 -Force } | Should Throw "Timed out waiting for 2 probes to restart" + } + + It "times out waiting for some probes to restart" { + SetResponseAndClient "RestartProbeResponse" + + { Restart-Probe -Timeout 20 -Force } | Should Throw "Timed out waiting for 1 probe to restart" + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-PrtgCore.Tests.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-PrtgCore.Tests.ps1 new file mode 100644 index 00000000..98c57968 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Restart-PrtgCore.Tests.ps1 @@ -0,0 +1,23 @@ +. $PSScriptRoot\Support\Standalone.ps1 + +Describe "Restart-PrtgCore" -Tag @("PowerShell", "UnitTest") { + It "restarts the core server" { + + $startDate = (Get-Date).ToString("yyyy-MM-dd-HH-mm-ss") + + SetAddressValidatorResponse @( + "api/restartserver.htm?" + "api/table.xml?content=messages&" + + "columns=datetime,parent,status,sensor,device,group,probe,priority,message,type,tags,active,objid,name&" + + "count=500&filter_status=1&filter_dstart=$startDate&" + ) + + Restart-PrtgCore -Force -Wait -Timeout 3600 + } + + It "completes all waiting stages" { + SetResponseAndClient "RestartPrtgCoreResponse" + + Restart-PrtgCore -Force + } +} \ No newline at end of file diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Support/UnitTest.ps1 b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Support/UnitTest.ps1 index 67803a32..50fd5ee3 100644 --- a/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Support/UnitTest.ps1 +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/PowerShell/Support/UnitTest.ps1 @@ -87,6 +87,40 @@ function SetActionResponse SetResponseAndClientWithArguments "BasicResponse" "" } +function SetAddressValidatorResponse($strArr, $exactMatch = $false) +{ + $arr = $null + + if($strArr -is [System.Array]) + { + $exactMatch = $true + + $arr = @($strArr | foreach { + BuildStr $_ + }) + } + else + { + if($exactMatch) + { + $arr = BuildStr $strArr + } + else + { + $arr = $strArr + } + } + + SetResponseAndClientWithArguments "AddressValidatorResponse" @($arr, $exactMatch) +} + +function BuildStr($str) +{ + $str = "https://prtg.example.com/" + $str + "username=username&passhash=12345678" + + return $str +} + function SetResponseAndClient($responseName) { $response = New-Object PrtgAPI.Tests.UnitTests.ObjectTests.TestResponses.$responseName diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/AddressValidatorResponse.cs b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/AddressValidatorResponse.cs index a4f6b8c4..287e1b5a 100644 --- a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/AddressValidatorResponse.cs +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/AddressValidatorResponse.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; using PrtgAPI.Tests.UnitTests.InfrastructureTests.Support; namespace PrtgAPI.Tests.UnitTests.ObjectTests.TestResponses @@ -7,15 +8,54 @@ public class AddressValidatorResponse : MultiTypeResponse { private string str; + public string[] strArray; + + private bool exactMatch; + + private int arrayPos; + public AddressValidatorResponse(string str) { this.str = str; + exactMatch = false; + } + + public AddressValidatorResponse(object str, bool exactMatch) + { + if (str is object[]) + strArray = ((object[])str).Cast().ToArray(); + else + this.str = (string)str; + + this.exactMatch = exactMatch; } protected override IWebResponse GetResponse(ref string address, string function) { - if (!address.Contains(str)) - Assert.Fail($"Address '{address}' did not contain '{str}'"); + if (exactMatch) + { + if (strArray != null) + { + if (arrayPos >= strArray.Length) + { + Assert.Fail($"Request for address '{address}' was not expected"); + } + + if(address != strArray[arrayPos]) + Assert.AreEqual(strArray[arrayPos], address, "Address was not correct"); + + arrayPos++; + } + else + { + Assert.AreEqual(str, address, "Address was not correct"); + } + } + else + { + if (!address.Contains(str)) + Assert.Fail($"Address '{address}' did not contain '{str}'"); + } return base.GetResponse(ref address, function); } diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/MultiTypeResponse.cs b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/MultiTypeResponse.cs index cc94bb7e..8021eb9f 100644 --- a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/MultiTypeResponse.cs +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/MultiTypeResponse.cs @@ -56,6 +56,14 @@ protected virtual IWebResponse GetResponse(ref string address, string function) return GetObjectDataResponse(address); case nameof(XmlFunction.GetObjectProperty): return GetRawObjectProperty(address); + case nameof(CommandFunction.ClearCache): + case nameof(CommandFunction.LoadLookups): + case nameof(CommandFunction.RecalcCache): + case nameof(CommandFunction.RestartServer): + case nameof(CommandFunction.RestartProbes): + case nameof(CommandFunction.ReloadFileLists): + case nameof(CommandFunction.SaveNow): + return new BasicResponse(string.Empty); default: throw GetUnknownFunctionException(function); } diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartProbeResponse.cs b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartProbeResponse.cs new file mode 100644 index 00000000..d3b65848 --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartProbeResponse.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using PrtgAPI.Helpers; +using PrtgAPI.Tests.UnitTests.InfrastructureTests.Support; +using PrtgAPI.Tests.UnitTests.ObjectTests.TestItems; + +namespace PrtgAPI.Tests.UnitTests.ObjectTests.TestResponses +{ + public class RestartProbeResponse : MultiTypeResponse + { + private ProbeItem[] probes; + + private int logRequestCount; + + protected override IWebResponse GetResponse(ref string address, string function) + { + switch (function) + { + case nameof(XmlFunction.TableData): + return GetTableResponse(address); + case nameof(CommandFunction.RestartProbes): + return new BasicResponse(string.Empty); + default: + throw GetUnknownFunctionException(function); + } + } + + private IWebResponse GetTableResponse(string address) + { + var components = UrlHelpers.CrackUrl(address); + + Content content = components["content"].ToEnum(); + + switch (content) + { + case Content.ProbeNode: + return GetProbeResponse(); + case Content.Messages: + return GetMessageResponse(); + default: + throw new NotImplementedException($"Unknown content '{content}' requested from {nameof(RestartProbeResponse)}"); + } + } + + private IWebResponse GetProbeResponse() + { + probes = Enumerable.Range(0, 2).Select(i => new ProbeItem(objid: i.ToString())).ToArray(); + + return new ProbeResponse(probes); + } + + private IWebResponse GetMessageResponse() + { + logRequestCount++; + + MessageResponse response; + + //Disconnect both probes, then reconnect them again + + switch (logRequestCount) + { + case 1: + response = new MessageResponse(); + break; + + case 2: + response = new MessageResponse(new MessageItem(objid: probes[0].ObjId, statusRaw: "613")); + break; + + case 3: + response = new MessageResponse(new MessageItem(objid: probes[1].ObjId, statusRaw: "613")); + break; + + case 4: + response = new MessageResponse(new MessageItem(objid: probes[0].ObjId, statusRaw: "612")); + break; + + case 5: + response = new MessageResponse(new MessageItem(objid: probes[1].ObjId, statusRaw: "612")); + break; + + default: + throw new NotImplementedException($"Handler missing for request #{logRequestCount}"); + } + + return response; + } + } +} diff --git a/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartPrtgCoreResponse.cs b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartPrtgCoreResponse.cs new file mode 100644 index 00000000..b8ef507d --- /dev/null +++ b/PrtgAPI.Tests.UnitTests/ObjectTests/TestResponses/RestartPrtgCoreResponse.cs @@ -0,0 +1,51 @@ +using System; +using System.Net; +using System.Net.Http; +using PrtgAPI.PowerShell.Cmdlets; +using PrtgAPI.Tests.UnitTests.InfrastructureTests.Support; + +namespace PrtgAPI.Tests.UnitTests.ObjectTests.TestResponses +{ + public class RestartPrtgCoreResponse : MultiTypeResponse + { + private RestartPrtgCore.RestartStage restartStage = RestartPrtgCore.RestartStage.Shutdown; + + private int shutdownRequestCount; + + protected override IWebResponse GetResponse(ref string address, string function) + { + switch (function) + { + case nameof(CommandFunction.RestartServer): + return new BasicResponse(string.Empty); + default: + return GetRestartServerProgressResponse(); + } + } + + private IWebResponse GetRestartServerProgressResponse() + { + switch (restartStage) + { + case RestartPrtgCore.RestartStage.Shutdown: + if (shutdownRequestCount == 0) + { + shutdownRequestCount++; + return new MessageResponse(); + } + restartStage = RestartPrtgCore.RestartStage.Restart; + throw new WebException(); + + case RestartPrtgCore.RestartStage.Restart: + restartStage = RestartPrtgCore.RestartStage.Startup; + throw new HttpRequestException(); + + case RestartPrtgCore.RestartStage.Startup: + return new MessageResponse(new TestItems.MessageItem(statusRaw: "1")); + + default: + throw new NotImplementedException($"Unknown stage '{restartStage}'"); + } + } + } +} diff --git a/PrtgAPI/Enums/ConfigFileType.cs b/PrtgAPI/Enums/ConfigFileType.cs new file mode 100644 index 00000000..9ffcb1e0 --- /dev/null +++ b/PrtgAPI/Enums/ConfigFileType.cs @@ -0,0 +1,18 @@ +namespace PrtgAPI +{ + /// + /// Specifies types of config files that can be loaded by PRTG. + /// + public enum ConfigFileType + { + /// + /// Device icons, report templates and language files. + /// + General, + + /// + /// Custom lookups used for customizing the display of sensor channel values. + /// + Lookups + } +} diff --git a/PrtgAPI/Enums/Functions/CommandFunction.cs b/PrtgAPI/Enums/Functions/CommandFunction.cs index 8f18a844..cb86e58b 100644 --- a/PrtgAPI/Enums/Functions/CommandFunction.cs +++ b/PrtgAPI/Enums/Functions/CommandFunction.cs @@ -59,6 +59,27 @@ enum CommandFunction [Undocumented] [Description("addsensor5.htm")] - AddSensor5 + AddSensor5, + + [Description("restartprobes.htm")] + RestartProbes, + + [Description("restartserver.htm")] + RestartServer, + + [Description("savenow.htm")] + SaveNow, + + [Description("clearcache.htm")] + ClearCache, + + [Description("recalccache.htm")] + RecalcCache, + + [Description("reloadfilelists.htm")] + ReloadFileLists, + + [Description("loadlookups.htm")] + LoadLookups } } diff --git a/PrtgAPI/Enums/SystemCacheType.cs b/PrtgAPI/Enums/SystemCacheType.cs new file mode 100644 index 00000000..d53dea3f --- /dev/null +++ b/PrtgAPI/Enums/SystemCacheType.cs @@ -0,0 +1,18 @@ +namespace PrtgAPI +{ + /// + /// Specifies types of caches that exist on a PRTG Network Monitor server. + /// + public enum SystemCacheType + { + /// + /// Cached data for Geo Maps and Active Directory Authentication. + /// + General, + + /// + /// Cached data for displaying graph data. Note: if this cache data is cleared, PRTG will be restarted. + /// + GraphData + } +} diff --git a/PrtgAPI/Parameters/ObjectManipulation/RestartProbeParameters.cs b/PrtgAPI/Parameters/ObjectManipulation/RestartProbeParameters.cs new file mode 100644 index 00000000..08f8469d --- /dev/null +++ b/PrtgAPI/Parameters/ObjectManipulation/RestartProbeParameters.cs @@ -0,0 +1,11 @@ +namespace PrtgAPI.Parameters +{ + class RestartProbeParameters : Parameters + { + public RestartProbeParameters(int? objectId) + { + if (objectId != null) + this[Parameter.Id] = objectId; + } + } +} diff --git a/PrtgAPI/PowerShell/Base/PrtgCmdlet.cs b/PrtgAPI/PowerShell/Base/PrtgCmdlet.cs index fa443c44..9e57d1f0 100644 --- a/PrtgAPI/PowerShell/Base/PrtgCmdlet.cs +++ b/PrtgAPI/PowerShell/Base/PrtgCmdlet.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using PrtgAPI.PowerShell.Progress; @@ -102,5 +103,15 @@ private void OnLogVerbose(object sender, LogVerboseEventArgs args) } #endregion + +#if DEBUG +#pragma warning disable 1591 + [ExcludeFromCodeCoverage] + protected bool UnitTest() +#pragma warning restore 1591 + { + return client.Server == "prtg.example.com"; + } +#endif } } diff --git a/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/BackupPrtgConfig.cs b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/BackupPrtgConfig.cs new file mode 100644 index 00000000..07993948 --- /dev/null +++ b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/BackupPrtgConfig.cs @@ -0,0 +1,39 @@ +using System.Management.Automation; +using PrtgAPI.PowerShell.Base; + +namespace PrtgAPI.PowerShell.Cmdlets +{ + /// + /// Requests a PRTG Network Monitor server create a backup of its configuration database. + /// + /// The Backup-PrtgConfig cmdlet requests a PRTG Network Monitor server create a backup of its configuration + /// database after first writing the current running configuration to disk. Configuration backups are stored under the + /// Configuration Auto-Backups folder, by default located at C:\ProgramData\Paessler\PRTG Network Monitor\Configuration Auto-Backups + /// + /// When this cmdlet is executed, PRTG will asynchronously start creating a configuration backup. As such, + /// this cmdlet will return before the backup has completed. Depending on the size of your configuration database, this may take + /// several seconds to complete. In order to guarantee a backup has been successfully completed, it is recommended to probe the contents + /// of the Configuration Auto-Backups folder before and after running this script, to identify the backup file that was created + /// as a result of running this cmdlet. The created backup can also be identified by parsing the DateTime timestamp in the created filename, + /// typically "PRTG Configuration (Snapshot yyyy-MM-dd HH-mm-ss)" + /// + /// + /// C:\> Backup-PrtgConfig + /// Trigger a backup of the PRTG Configuration Database. + /// + /// + [Cmdlet(VerbsData.Backup, "PrtgConfig", SupportsShouldProcess = true)] + public class BackupPrtgConfig : PrtgCmdlet + { + /// + /// Performs record-by-record processing functionality for the cmdlet. + /// + protected override void ProcessRecordEx() + { + if (ShouldProcess("Backup PRTG Configuration")) + { + client.BackupConfigDatabase(); + } + } + } +} diff --git a/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/ClearPrtgCache.cs b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/ClearPrtgCache.cs new file mode 100644 index 00000000..e3598871 --- /dev/null +++ b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/ClearPrtgCache.cs @@ -0,0 +1,82 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using PrtgAPI.PowerShell.Base; + +namespace PrtgAPI.PowerShell.Cmdlets +{ + /// + /// Clears cached data used by a PRTG Network Monitor server. + /// + /// The Clear-PrtgCache cmdlet clears Geo Map, Active Directory authentication + /// and Graph Data details from a PRTG server. Geo Map and Active Directory authentication caches are grouped + /// together under the cache type. Graph Data is cleared separately, + /// under the cache type. + /// + /// When Graph Data is cleared, the PRTG Core Service will automatically restart. + /// To prevent this happening accidentally, Clear-PrtgCache will prompt you to confirm you are sure you wish to proceed. + /// This can be overridden by specifying the - parameter. + /// + /// + /// C:\> Clear-PrtgCache General + /// Clear Geo Map and Active Directory authentication caches. + /// + /// + /// + /// C:\> Clear-PrtgCache GraphData -Force + /// Clear PRTG's graph data cache without displaying a confirmation prompt that this will restart the PRTG Core Service. + /// + /// + [Cmdlet(VerbsCommon.Clear, "PrtgCache", SupportsShouldProcess = true)] + public class ClearPrtgCache : PrtgCmdlet + { + /// + /// The type of cache to clear. + /// + [Parameter(Mandatory = true, Position = 0)] + public SystemCacheType Type { get; set; } + + /// + /// Forces PRTG to clear caches that may cause a reboot of the PRTG Server without displaying a confirmation prompt. + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Performs record-by-record processing functionality for the cmdlet. + /// + protected override void ProcessRecordEx() + { + if (ShouldProcess($"Clear PRTG {GetCacheDescription(Type)} Cache")) + { + if (Type == SystemCacheType.GraphData) + { + bool yes = false; + bool no = false; + + if (Force || ShouldContinue("Clearing the graph cache will restart the PRTG Core Service. All users will be disconnected while restart is in progress. Are you sure you wish to continue?", "WARNING!", true, ref yes, ref no)) + { + client.ClearSystemCache(Type); + } + } + else + { + client.ClearSystemCache(Type); + } + } + + client.ClearSystemCache(Type); + } + + [ExcludeFromCodeCoverage] + private string GetCacheDescription(SystemCacheType cacheType) + { + if (cacheType == SystemCacheType.General) + return "Map & Authentication"; + if (cacheType == SystemCacheType.GraphData) + return "Graph Data"; + + throw new NotImplementedException($"Don't know how to handle cache type '{cacheType}'"); + } + } +} diff --git a/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/LoadPrtgConfigFile.cs b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/LoadPrtgConfigFile.cs new file mode 100644 index 00000000..9e7e849a --- /dev/null +++ b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/LoadPrtgConfigFile.cs @@ -0,0 +1,59 @@ +using System; +using System.Management.Automation; +using PrtgAPI.PowerShell.Base; + +namespace PrtgAPI.PowerShell.Cmdlets +{ + /// + /// Reloads config files including sensor lookups and device icons used by PRTG Network Monitor. + /// + /// The Load-PrtgConfigFile cmdlet reloads miscellaneous config files used by PRTG Network Monitor, + /// including lookup definitions used for customizing how sensor values are displayed, as well as other miscellaneous config + /// files including device icons, report templates and language files. + /// Config files used by PRTG are automatically reloaded whenever the PRTG Core Service is restarted. + /// To prevent having to completely restart PRTG whenever a config file is installed or modified, Load-PrtgConfigFile can instead be used. + /// Device icons, report templates and language files are refreshed together by a single request, + /// categorised by PrtgAPI as the config file type. Sensor lookups are reloaded separately, + /// under the file type. + /// + /// + /// C:\> Load-PrtgConfigFile General + /// Reload any device icons, report templates or language files that may have been installed or modified since PRTG started. + /// + /// + /// + /// C:\> Load-PrtgConfigFile Lookups + /// Loads or reloads any sensor lookups that may have been changed or installed since PRTG was started. + /// + /// + [Cmdlet(VerbsData.Sync, "PrtgConfigFile", SupportsShouldProcess = true)] + public class LoadPrtgConfigFile : PrtgCmdlet + { + /// + /// The type of config files to reload. + /// + [Parameter(Mandatory = true, Position = 0)] + public ConfigFileType Type { get; set; } + + /// + /// Performs record-by-record processing functionality for the cmdlet. + /// + protected override void ProcessRecordEx() + { + if (ShouldProcess($"Load PRTG {GetFileTypeDescription(Type)}")) + { + client.LoadConfigFiles(Type); + } + } + + private string GetFileTypeDescription(ConfigFileType fileType) + { + if (fileType == ConfigFileType.General) + return "Custom Files"; + if (fileType == ConfigFileType.Lookups) + return "Lookups"; + + throw new NotImplementedException($"Don't know how to handle file type '{fileType}'"); + } + } +} diff --git a/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartProbe.cs b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartProbe.cs new file mode 100644 index 00000000..61eaaeb5 --- /dev/null +++ b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartProbe.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Management.Automation; +using System.Threading; +using PrtgAPI.PowerShell.Base; + +namespace PrtgAPI.PowerShell.Cmdlets +{ + /// + /// Restarts the PRTG Probe Service of PRTG Network Monitor probes. + /// + /// The Restart-Probe cmdlet restarts the PRTG Probe Service of a specified PRTG Probe. If no probe is specified, Restart-Probe + /// will restart the PRTG Probe Service of all PRTG Probes in your environment. + /// When executed, Restart-Probe will prompt to confirm you wish to restart the PRTG Probe Service of each PRTG Probe. + /// Within this prompt you may respond to each probe individually or answer yes/no to all. To override this prompt completely, the - + /// parameter can be specified. + /// By default, Restart-Probe will wait for one hour for all probes restart and reconnect to PRTG. + /// If you do not wish to wait at all, this can be overridden by specifying -:$false. You may additionally specify a custom + /// timeout duration (in seconds) via the - parameter. If Restart-Probe times out waiting for a PRTG Probe Service to restart, + /// a will be thrown specifying the number of probes that failed to restart. + /// + /// + /// C:\> Restart-Probe + /// Restart all probes on a PRTG Server and wait for them to restart. + /// + /// + /// + /// C:\> Get-Probe *contoso* | Restart-Probe -Timeout 180 -Force + /// Restart all probes containing "contoso" in their names, waiting 3 minutes for them to restart, without displaying a confirmation prompt. + /// + /// + /// + /// Get-Probe -Id 2004 | Restart-Probe -Wait:$false + /// Restart the probe with ID 2004, without waiting for the probe to restart. + /// + /// + /// Get-Probe + /// Restart-PrtgCore + /// + [Cmdlet(VerbsLifecycle.Restart, "Probe", SupportsShouldProcess = true)] + public class RestartProbe : PrtgCmdlet + { + /// + /// The probe to restart. If no probe is specified, all probes will be restarted. + /// + [Parameter(Mandatory = false, ValueFromPipeline = true)] + public Probe Probe { get; set; } + + /// + /// Forces the PRTG Probe Service to be restarted on all specified probes without displaying a confirmation prompt. + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Wait for the PRTG Core Service to restart before ending the cmdlet. By default this value is true. + /// + [Parameter(Mandatory = false)] + public SwitchParameter Wait { get; set; } = SwitchParameter.Present; + + /// + /// Duration (in seconds) to wait for the PRTG Probe Service of all probes to restart. Default value is 3600 (1 hour). If is false, this parameter will have no effect. + /// + [Parameter(Mandatory = false)] + public int Timeout { get; set; } = 3600; + + private bool yesToAll; + private bool noToAll; + + private DateTime? restartTime; + + private int secondsRemaining = 150; + private int secondsElapsed; + + private List probesRestarted = new List(); + + /// + /// Performs record-by-record processing functionality for the cmdlet. + /// + protected override void ProcessRecordEx() + { + if (Probe == null) + { + if (ShouldProcess("All PRTG Probes")) + { + var count = client.GetTotalObjects(Content.ProbeNode); + if (Force || yesToAll || ShouldContinue($"Are you want to restart the PRTG Probe Service on all {count} PRTG Probes?", "WARNING", true, ref yesToAll, ref noToAll)) + { + restartTime = DateTime.Now; + client.RestartProbe(null); + } + } + } + else + { + if (ShouldProcess($"'{Probe.Name}' (ID: {Probe.Id})")) + { + if (Force || yesToAll || ShouldContinue($"Are you sure you want to restart the PRTG Probe Service on probe '{Probe.Name}' (ID: {Probe.Id})?", "WARNING", true, ref yesToAll, ref noToAll)) + { + restartTime = DateTime.Now; + probesRestarted.Add(Probe); + client.RestartProbe(Probe.Id); + } + } + } + } + + /// + /// Provides a one-time, postprocessing functionality for the cmdlet. + /// + protected override void EndProcessing() + { + if (Wait && restartTime != null) + { + if (Probe == null) + GetProgressForAllProbes(); + else + GetProgressForRestartedProbes(probesRestarted); + } + } + + private void GetProgressForAllProbes() + { + var probes = client.GetProbes(); + + GetProgressForRestartedProbes(probes); + } + + private void GetProgressForRestartedProbes(List probes) + { + List probeStatuses = probes.Select(p => new RestartedProbe(p)).ToList(); + + while (probeStatuses.Any(p => p.Reconnected == false)) + { + //Get all logs relating to probes connecting and disconnecting since we initiated the restarts. + //If we've already detected all probes have disconnected, no need to include those logs in the response + var statuses = new List { LogStatus.Connected }; + + if (probeStatuses.Any(p => !p.Disconnected)) + statuses.Add(LogStatus.Disconnected); + + var logs = client.GetLogs(null, endDate: restartTime, status: statuses.ToArray()); + + UpdateProbeStatus(probeStatuses, logs); + + WriteProbeProgress(probeStatuses); + } + } + + private void UpdateProbeStatus(List probes, List logs) + { + foreach (var probe in probes) + { + if (!probe.Disconnected) + { + //If we got a log saying the probe disconnected, or the probe was already disconnected, flag it as having disconnected + if (logs.Any(log => log.Status == LogStatus.Disconnected && log.Id == probe.Id) || probe.Condition == ProbeStatus.Disconnected) + probe.Disconnected = true; + } + if (probe.Disconnected && !probe.Reconnected) + { + if (logs.Any(log => log.Status == LogStatus.Connected && log.Id == probe.Id)) + probe.Reconnected = true; + } + } + } + + private void WriteProbeProgress(List probeStatuses) + { + var completed = probeStatuses.Count(p => p.Reconnected) + 1; + + if (completed > probeStatuses.Count) + completed--; + + var completedPercent = completed / probeStatuses.Count * 100; + + var record = new ProgressRecord(1, "Restart PRTG Probes", $"Restarting all probes {completed}/{probeStatuses.Count}") + { + PercentComplete = completedPercent, + SecondsRemaining = secondsRemaining, + CurrentOperation = "Waiting for all probes to restart" + }; + + for (int i = 0; i < 5; i++) + { + WriteProgress(record); + + secondsRemaining--; + secondsElapsed++; + + if (secondsRemaining < 0) + secondsRemaining = 30; + + record.SecondsRemaining = secondsRemaining; + + if (secondsElapsed > Timeout) + { + var remaining = probeStatuses.Count - completed + 1; + + var plural = remaining == 1 ? "probe" : "probes"; + + throw new TimeoutException($"Timed out waiting for {remaining} {plural} to restart"); + } + +#if DEBUG + if(!UnitTest()) + Thread.Sleep(1000); +#else + Thread.Sleep(1000); +#endif + } + } + + class RestartedProbe + { + private Probe probe { get; set; } + + public int Id => probe.Id; + + public ProbeStatus Condition => probe.Condition; + + public bool Disconnected { get; set; } + + public bool Reconnected { get; set; } + + public RestartedProbe(Probe probe) + { + this.probe = probe; + } + } + } +} diff --git a/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartPrtgCore.cs b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartPrtgCore.cs new file mode 100644 index 00000000..f0c0f01b --- /dev/null +++ b/PrtgAPI/PowerShell/Cmdlets/ObjectManipulation/RestartPrtgCore.cs @@ -0,0 +1,189 @@ +using System; +using System.Linq; +using System.Management.Automation; +using System.Net; +using System.Net.Http; +using System.Threading; +using PrtgAPI.PowerShell.Base; + +namespace PrtgAPI.PowerShell.Cmdlets +{ + /// + /// Restarts the PRTG Core Service of a PRTG Network Monitor server. + /// + /// The Restart-PrtgCore cmdlet restarts the PRTG Core Service of a PRTG Network Monitor Server. + /// Upon restarting the service, all monitoring will cease functioning and all users will be completely disconnected. + /// If PRTG is in a cluster setup, this cmdlet will only restart the PRTG Core Service of the server PrtgAPI is connected to. + /// + /// When executed, Restart-PrtgCore will prompt to confirm you wish to restart the PRTG Core Service. To override this prompt, the - + /// parameter can be specified.By default, Restart-PrtgCore will one hour for the PRTG Core Service to completely restart and come back online. If you do not wish + /// to wait at all, this can be overridden by specifying -:$false. You may additionally specify a custom timeout duration (in seconds) + /// via the - parameter. If Restart-PrtgCore times out waiting for the PRTG Core Service to restart, a + /// will be thrown. + /// Extreme caution should be used when using Restart-PrtgCore. While smaller PRTG installs can restart in a matter of minutes, + /// servers containing in excess of 10,000 sensors can take over half an hour to restart. + /// + /// + /// C:\> Restart-PrtgCore + /// Restart the PRTG Core Service. The cmdlet will wait up to 60 minutes for the service to restart. + /// + /// + /// + /// C:\> Restart-PrtgCore -Wait:$false + /// Restart the PRTG Core Service, without waiting for the service to restart before ending the cmdlet. + /// + /// + /// + /// C:\> Restart-PrtgCore -Timeout 600 + /// Restart the PRTG Core Service, waiting 10 minutes (600 seconds) for the service to restart. + /// + /// + /// Restart-Probe + /// + [Cmdlet(VerbsLifecycle.Restart, "PrtgCore", SupportsShouldProcess = true)] + public class RestartPrtgCore : PrtgCmdlet + { + /// + /// Forces the PRTG Core Service to be restarted without displaying a confirmation prompt. + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Wait for the PRTG Core Service to restart before ending the cmdlet. By default this value is true. + /// + [Parameter(Mandatory = false)] + public SwitchParameter Wait { get; set; } = SwitchParameter.Present; + + /// + /// Duration (in seconds) to wait for the PRTG Core Service to restart. Default value is 3600 (1 hour). If is false, this parameter will have no effect. + /// + [Parameter(Mandatory = false, HelpMessage = "Duration (in seconds) to wait for the PRTG Core Service to restart. Default value is 3600 (1 hour). If -Wait is false, this parameter will have no effect.")] + public int Timeout { get; set; } = 3600; + + private DateTime restartTime; + + private int secondsRemaining = 150; + private int secondsElapsed; + + /// + /// Performs record-by-record processing functionality for the cmdlet. + /// + protected override void ProcessRecordEx() + { + if (ShouldProcess("PRTG Core Service")) + { + bool yes = false; + bool no = false; + if (Force || ShouldContinue("Are you sure you want to restart the PRTG Core Service? All users will be disconnected while restart is in progress", "WARNING!", true, ref yes, ref no)) + { + restartTime = DateTime.Now; + + client.RestartCore(); + + if (Wait) + { + WaitForRestart(); + } + } + } + } + + private void WaitForRestart() + { + var record = new ProgressRecord(1, "Restart PRTG Core", "Restarting PRTG Core") + { + CurrentOperation = "Waiting for PRTG Core Server to shutdown" + }; + + RestartStage stage = RestartStage.Shutdown; + + var originalRetries = client.RetryCount; + + try + { + client.RetryCount = 0; + + while (stage != RestartStage.Completed) + { + stage = ProbeServer(); + + switch (stage) + { + case RestartStage.Shutdown: + break; + case RestartStage.Restart: + record.CurrentOperation = "Waiting for PRTG Core Service to restart"; + break; + case RestartStage.Startup: + record.CurrentOperation = "Waiting for PRTG Core Server to initialize"; + break; + } + + if (stage == RestartStage.Completed) + break; + + record.PercentComplete = (int) ((double) stage/3*100); + record.SecondsRemaining = secondsRemaining; + + for (int i = 0; i < 5; i++) + { + WriteProgress(record); + + secondsRemaining--; + secondsElapsed++; + + if (secondsRemaining < 0) + secondsRemaining = 30; + + record.SecondsRemaining = secondsRemaining; + + if (secondsElapsed > Timeout) + throw new TimeoutException($"Timed out waiting for PRTG Core Service to restart"); + +#if !DEBUG + Thread.Sleep(1000); +#endif + } + + if (Stopping) + break; + } + } + finally + { + client.RetryCount = originalRetries; + } + } + + private RestartStage ProbeServer() + { + RestartStage stage; + + try + { + var logs = client.GetLogs(null, endDate: restartTime, status: LogStatus.SystemStart); + + stage = logs.Any() ? RestartStage.Completed : RestartStage.Shutdown; + } + catch (Exception ex) when (ex is WebException || ex is TimeoutException) + { + stage = RestartStage.Restart; + } + catch (HttpRequestException) + { + stage = RestartStage.Startup; + } + + return stage; + } + + internal enum RestartStage + { + Shutdown = 1, + Restart = 2, + Startup = 3, + Completed + } + } +} diff --git a/PrtgAPI/PowerShell/Resources/PrtgAPI.psm1 b/PrtgAPI/PowerShell/Resources/PrtgAPI.psm1 index 841fc1fa..cb970b37 100644 --- a/PrtgAPI/PowerShell/Resources/PrtgAPI.psm1 +++ b/PrtgAPI/PowerShell/Resources/PrtgAPI.psm1 @@ -18,6 +18,7 @@ New-Alias Clone-Group Copy-Group New-Alias Clone-Device Copy-Device New-Alias Sort-PrtgObject Start-SortPrtgObject New-Alias Simulate-ErrorStatus Test-ErrorStatus +New-Alias Load-PrtgConfigFile Sync-PrtgConfigFile New-Alias Connect-GoPrtg Connect-GoPrtgServer New-Alias Install-GoPrtg Install-GoPrtgServer @@ -27,6 +28,8 @@ New-Alias GoPrtg Connect-GoPrtgServer New-Alias flt New-SearchFilter +New-Alias Restart-PrtgProbe Restart-Probe + $ErrorActionPreference = "Stop" $functions = Get-ChildItem "$PSScriptRoot\Functions" diff --git a/PrtgAPI/PrtgClient.cs b/PrtgAPI/PrtgClient.cs index 8497e12c..f6e3fd61 100644 --- a/PrtgAPI/PrtgClient.cs +++ b/PrtgAPI/PrtgClient.cs @@ -1666,6 +1666,117 @@ private async Task CreateSetObjectPropertyParameter return parameters; } + #endregion + #region System Administration + + /// + /// Request PRTG generate a backup of the PRTG Configuration Database. + /// When executed, this method will request PRTG store a backup of its configuration database under + /// the Configuration Auto-Backups folder after first writing the current running configuration to disk. + /// Depending on the size of your database, this may take several seconds to complete. Note that PRTG always creates + /// its backup asynchronously; as such when this method returns the backup may not have fully completed. + /// By default, configuration backups are stored under C:\ProgramData\Paessler\PRTG Network Monitor\Configuration Auto-Backups. + /// + public void BackupConfigDatabase() => + requestEngine.ExecuteRequest(CommandFunction.SaveNow, new Parameters.Parameters()); + + /// + /// Asynchronously request PRTG generate a backup of the PRTG Configuration Database. + /// When executed, this method will request PRTG store a backup of its configuration database under + /// the Configuration Auto-Backups folder after first writing the current running configuration to disk. + /// Depending on the size of your database, this may take several seconds to complete. Note that PRTG always creates + /// its backup asynchronously; as such when this method returns the backup may not have fully completed. + /// By default, configuration backups are stored under C:\ProgramData\Paessler\PRTG Network Monitor\Configuration Auto-Backups. + /// + public async Task BackupConfigDatabaseAsync() => + await requestEngine.ExecuteRequestAsync(CommandFunction.SaveNow, new Parameters.Parameters()).ConfigureAwait(false); + + /// + /// Clear cached data used by PRTG, including map, graph and authentication caches. Note: clearing certain cache types may result in a restart of the PRTG Core Server. + /// See each cache type for further details. + /// + /// The type of cache to clear. Note: clearing certain cache types may result in a restart of the PRTG Core Server. + /// See each cache type for further details. + public void ClearSystemCache(SystemCacheType cache) => + requestEngine.ExecuteRequest(GetClearSystemCacheFunction(cache), new Parameters.Parameters()); + + /// + /// Asynchronously clear cached data used by PRTG, including map, graph and authentication caches. Note: clearing certain cache types may result in a restart of the PRTG Core Server. + /// See each cache type for further details. + /// + /// The type of cache to clear. Note: clearing certain cache types may result in a restart of the PRTG Core Server. + /// See each cache type for further details. + public async Task ClearSystemCacheAsync(SystemCacheType cache) => + await requestEngine.ExecuteRequestAsync(GetClearSystemCacheFunction(cache), new Parameters.Parameters()).ConfigureAwait(false); + + [ExcludeFromCodeCoverage] + private CommandFunction GetClearSystemCacheFunction(SystemCacheType cache) + { + if (cache == SystemCacheType.General) + return CommandFunction.ClearCache; + if (cache == SystemCacheType.GraphData) + return CommandFunction.RecalcCache; + + throw new NotImplementedException($"Don't know how to handle cache type '{cache}'"); + } + + /// + /// Reload config files including sensor lookups, device icons and report templates used by PRTG. + /// + /// The type of files to reload. + public void LoadConfigFiles(ConfigFileType fileType) => + requestEngine.ExecuteRequest(GetLoadSystemFilesFunction(fileType), new Parameters.Parameters()); + + /// + /// Asymchronously reload config files including sensor lookups, device icons and report templates used by PRTG. + /// + /// The type of files to reload. + public async Task LoadConfigFilesAsync(ConfigFileType fileType) => + await requestEngine.ExecuteRequestAsync(GetLoadSystemFilesFunction(fileType), new Parameters.Parameters()).ConfigureAwait(false); + + [ExcludeFromCodeCoverage] + private CommandFunction GetLoadSystemFilesFunction(ConfigFileType fileType) + { + if (fileType == ConfigFileType.General) + return CommandFunction.ReloadFileLists; + if (fileType == ConfigFileType.Lookups) + return CommandFunction.LoadLookups; + + throw new NotImplementedException($"Don't know how to handle file type '{fileType}'"); + } + + /// + /// Restarts the PRTG Probe Service of a specified PRTG Probe. If no probe ID is specified, the PRTG Probe Service will be restarted on all PRTG Probes. + /// + /// + public void RestartProbe(int? probeId) => + requestEngine.ExecuteRequest(CommandFunction.RestartProbes, new RestartProbeParameters(probeId)); + + /// + /// Asynchronously restarts the PRTG Probe Service of a specified PRTG Probe. If no probe ID is specified, the PRTG Probe Service will be restarted on all PRTG Probes. + /// + /// + public async Task RestartProbeAsync(int? probeId) => + await requestEngine.ExecuteRequestAsync(CommandFunction.RestartProbes, new RestartProbeParameters(probeId)).ConfigureAwait(false); + + /// + /// Restarts the PRTG Core Service. This will cause PRTG to disconnect all users and become completely unavailable while the service restarts. + /// If PRTG is part of a cluster, only the server specified by the current will be restarted. + /// + public void RestartCore() + { + requestEngine.ExecuteRequest(CommandFunction.RestartServer, new Parameters.Parameters()); + } + + /// + /// Asynchronously restarts the PRTG Core Service. This will cause PRTG to disconnect all users and become completely unavailable while the service restarts. + /// If PRTG is part of a cluster, only the server specified by the current will be restarted. + /// + public async Task RestartCoreAsync() + { + await requestEngine.ExecuteRequestAsync(CommandFunction.RestartServer, new Parameters.Parameters()).ConfigureAwait(false); + } + #endregion #region Miscellaneous