PoShPRTG.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PoShPRTG.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName PoShPRTG.Import.DoDotSource -Fallback $false
if ($PoShPRTG_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName PoShPRTG.Import.IndividualFiles -Fallback $false
if ($PoShPRTG_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }

function Import-ModuleFile {
    <#
        .SYNOPSIS
            Loads files into the module on module import.
 
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
 
            This provides a central location to react to files being imported, if later desired
 
        .PARAMETER Path
            The path to the file to load
 
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
 
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )

    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) {
        . $resolvedPath
    } else {
        $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null)
    }
}

#region Load individual files
if ($importIndividualFiles) {
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'PoShPRTG' -Language 'en-US'


<#PSScriptInfo
.VERSION 1.0.0.4
.GUID 32a4f2d6-b021-4a38-8b6a-d76ceef2b02d
.AUTHOR Jeffrey Snover
.COMPANYNAME
.COPYRIGHT
.TAGS
.LICENSEURI
.PROJECTURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
#>

function Compare-ObjectProperty {
    <#
 
    .DESCRIPTION
        Determine the difference in properties between two objects.
 
        NOTE - if a property's type does not have a CompareTo() function, the property is converted to a
        string for the comparison
 
    .EXAMPLE
        $o1= @{a1=1;a2=2;b1=3;b2=4}
        PS C:\> $o2= @{a1=1;a2=2;b1=3;b2=5}
        PS C:\> Compare-ObjectProperty $o1 $o2
 
        Property Value SideIndicator
        -------- ----- -------------
        b2 4 <=
        b2 5 =>
 
    .EXAMPLE
        $o1= @{a1=1;a2=2;b1=3;b2=4}
        PS C:\> $o2= @{a1=1;a2=2;b1=3;b2=5}
        PS C:\> Compare-ObjectProperty $o1 $o2 -PropertyFilter a*
 
    .EXAMPLE
        $o1= @{a1=1;a2=2;b1=3;b2=4}
        PS C:\> $o2= @{a1=1;a2=2;b1=3;b2=5}
        PS C:\> Compare-ObjectProperty $o1 $o2 -IncludeEqual
        Property Value SideIndicator
        -------- ----- -------------
        a1 1 ==
        a2 2 ==
        b1 3 ==
        b2 4 <=
        b2 5 =>
    #>

    [CmdletBinding()]
    param(
        # ReferenceObject
        [Parameter(Mandatory = $true)]
        [object]
        ${ReferenceObject},

        # DifferenceObject
        [Parameter(Mandatory = $true)]
        [object]
        ${DifferenceObject},

        # You can specify which properties to compare using a wildcard (using the -LIKE operator)
        [String[]]
        ${PropertyFilter} = "*",

        # Don't include any properties that are different
        [switch]
        ${ExcludeDifferent},

        # Include the properties which are equal
        [switch]
        ${IncludeEqual}
    )


    #region Helper Routines
    function Convert-PropertyToHash {
        param([Parameter(Position = 0)]$inputObject)

        if ($null -eq $inputObject) {
            return @{}
        } elseif ($inputObject -is [HashTable]) {
            # We have to clone the hashtable because we are going to Remove Keys and if we don't
            # clone it, we'll modify the original object
            return $inputObject.clone()
        } else {
            $h = @{}
            foreach ($p in (Get-Member -InputObject $inputObject -MemberType Properties).Name) {
                $h.$p = $inputObject.$p
            }
            return $h
        }
    }
    #endregion

    $refH = Convert-PropertyToHash $ReferenceObject
    $diffH = Convert-PropertyToHash $DifferenceObject

    foreach ($filter in $PropertyFilter) {
        foreach ($p in $refH.keys | Where-Object { $_ -like $filter }) {
            if (! ($diffH.Contains($p)) -and !($ExcludeDifferent)) {
                New-Object PSObject -Property @{ SideIndicator = "<="; Property = $p; Value = $($refH.$p) }
            } else {
                # We convert these to strings and do a string comparison because there are all sorts of .NET
                # objects whose comparison functions don't yeild the expected results.
                if ($refH.$p -AND ($refH.$p | Get-Member -MemberType Method -Name CompareTo)) {
                    $Different = $refH.$p -ne $diffH.$p
                } else {
                    $Different = ("" + $refH.$p) -ne ("" + $diffH.$p)
                }
                if ($Different -and !($ExcludeDifferent)) {
                    New-Object PSObject -Property @{ SideIndicator = "<="; Property = $p; Value = $($refH.$p) }
                    New-Object PSObject -Property @{ SideIndicator = "=>"; Property = $p; Value = $($diffH.$p) }
                } elseif ($IncludeEqual) {
                    New-Object PSObject -Property @{ SideIndicator = "=="; Property = $p; Value = $($refH.$p) }
                }
                $diffH.Remove($p)
            }
        }
    }

    if (-not $ExcludeDifferent) {
        foreach ($p in $diffH.keys | Where-Object { $_ -like $PropertyFilter }) {
            New-Object PSObject -Property @{ SideIndicator = "=>"; Property = $p; Value = $($diffH.$p) }
        }
    }
}

function Set-TypesNamesToPRTGObject {
    <#
    .Synopsis
       Set-TypesNamesToPRTGObject
 
    .DESCRIPTION
       Add module specific type names to result objects of a function.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .EXAMPLE
        Set-TypesNamesToPRTGObject $PRTGObject
        Work on the specified object
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(ConfirmImpact="low", SupportsShouldProcess=$false)]
    param(
        # Object to work on
        $PRTGObject
    )

    begin {}

    process {
        foreach ($item in $PRTGObject) {
            if ($item.pstypenames[0] -eq "PRTG.Object.Compare") { $null = $item.pstypenames.Remove("PRTG.Object.Compare") }

            switch ($item.LocalName) {
                'probenode' {
                    if ($item.pstypenames -notcontains "PRTG.Object.Probenode") {
                        $item.pstypenames.Insert(0, "PRTG.Object.Probenode")
                    }
                }

                'group' {
                    if ($item.pstypenames -notcontains "PRTG.Object.Group") {
                        $item.pstypenames.Insert(0, "PRTG.Object.Group")
                    }
                }

                'device' {
                    if ($item.pstypenames -notcontains "PRTG.Object.Device") {
                        $item.pstypenames.Insert(0, "PRTG.Object.Device")
                    }
                }

                'sensor' {
                    if ($item.pstypenames -notcontains "PRTG.Object.Sensor") {
                        $item.pstypenames.Insert(0, "PRTG.Object.Sensor")
                    }
                }
            }

            if ($item.pstypenames -notcontains "PRTG.Object") { $item.pstypenames.Insert(1, "PRTG.Object") }
            if ($item.pstypenames -notcontains "PRTG") { $item.pstypenames.Insert(2, "PRTG") }

            $item
        }
    }

    end {}
}


function Write-Log {
    <#
    .Synopsis
       Write-Log / Log
       Logs text to the console and/or to a file.
 
    .DESCRIPTION
       A comprehensive helper function for structured logging.
       Writes one or more messages to the different available outputchannels of the powershell and to one or more logfiles.
 
    .NOTES
       Version: 2.4
       Author: Andreas Bellstedt
       History: 01.07.2016 - First Version
                    07.08.2016 - add logging to differnt output channels and more flexibility in parameters
                    14.08.2016 - changing synopsis position to powershell best practices. (before funktion block)
                    27.01.2017 - add parameters $Type and $logscope to easy logging structur and prevent the need of global variables for (status)types
                    29.01.2017 - change debug output procedure for better handling
 
    .EXAMPLE
       Examples without logging to a file. Only console output is done. The following examples only produces output
       if the verbosepreference in current session is set to "continue", or the -verbose switch is specified:
 
       Write-Log -LogText "This is a Message"
       Write-Log "This is a Message"
       Log "This is a Message"
       "This is a Message" | Write-Log
         -> VERBOSE: [2016-08-08 08:08:08] [NOFILE] This is a Message
 
       "This is a Message" , "This is anonther Message" | Write-Log -LogType Info
         -> VERBOSE: [2016-08-08 08:08:08] [NOFILE] [INFO ] This is a Message
            VERBOSE: [2016-08-08 08:08:08] [NOFILE] [INFO ] This is another Message
 
    .EXAMPLE
       Examples without logging to a file. Only console output is done. The following examples produces output
       irrespective of the verbosepreference:
 
       Write-Log -LogText "This is a Message" -LogType Warning -LogScope "Function01" -Warning
         -> WARNING: [2016-08-08 08:08:08] [NOFILE] [WARNING] [FUNCTION01] This is a Message
 
       Write-Log -LogText "This is a Message" -LogType Error -LogScope "Function01" -Error
         -> Write-Log : [2016-08-07 16:13:28] [NOFILE] [ERROR ] [FUNCTION01] This is a Message
            + Write-Log -LogText "This is a Message" -Error
            + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
                + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Write-Log
 
       Write-Log -LogText "This is a Message" -Console
       Write-Log -LogText "This is a Message" -Visible
         -> [2016-08-08 08:08:08] [NOFILE] This is a Message
 
       Write-Log -LogText "This is a Message" -Console -Warning
         -> WARNING: [2016-08-08 08:08:08] [NOFILE] This is a Message
 
       Write-Log -LogText "This is a Message" -Console -NoFileStatus
         -> [2016-08-08 08:08:08] This is a Message
 
       Write-Log -LogText "This is a Message" -Console -NoFileStatus -NoTimeStamp
         -> This is a Message
 
    .EXAMPLE
       Examples without logging to a file. Only console output is done. The following examples produces output
       to the debug channel:
 
       Write-Log -LogText "This is a Message" -DebugOutput
         -> DEBUG: [2016-08-08 08:08:08] [NOFILE] This is a Message
 
       Write-Log -LogText "This is a Message" -DebugOutput -Warning
         -> DEBUG: WARNING: [2016-08-08 08:08:08] [NOFILE] This is a Message
 
    .EXAMPLE
       Examples without logging to a file.
 
       Write-Log -LogText "This is a Message" -LogFile 'C:\Administration\Logs\Logfile.log'
         -> VERBOSE: [2016-08-08 08:08:08] [FILE ] This is a Message
 
       Write-Log -LogText "This is a Message" -LogFile 'C:\Administration\Logs\Logfile.log', 'C:\Administration\Logs\Logfile-Errors.log' -Warning
         -> WARNING: [2016-08-08 08:08:08] [FILE ] This is a Message
 
       Write-Log -LogText "This is a Message" -LogFile 'C:\Administration\Logs\Logfile.log', 'C:\Administration\Logs\Logfile-Errors.log' -Error
         -> Write-Log : [2016-08-08 08:08:08] [FILE ] This is a Message
            In Zeile:1 Zeichen:1
            + Write-Log -LogText "This is a Message" -LogFile 'C:\Administration\Lo ...
            + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
                + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Write-Log
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]

    [CmdletBinding(
        DefaultParameterSetName = 'VerboseOutput',
        ConfirmImpact = "Low"
    )]
    [Alias('Log')]
    Param(
        #The message to be logged
        [parameter( Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true )]
        [Alias('Text', 'Message')]
        [string]$LogText,

        #The kind of event/action what is happening while the message is logged
        [parameter( Mandatory = $false,
            Position = 1)]
        [Alias('Type')]
        [ValidateSet('Warning', 'Info', 'Query', 'Set', 'Error')]
        [string]$LogType,

        #The name of the function or the scriptpart where the log event happens
        [parameter( Mandatory = $false,
            Position = 2 )]
        [Alias('Scope')]
        [string]$LogScope,

        #The name of the logfile(s) where the message should be logged
        [parameter( Mandatory = $false )]
        [Alias('File')]
        [string[]]$LogFile,

        #Suppress the timestamp in the logged output
        [parameter( Mandatory = $false )]
        [switch]$NoTimeStamp,

        #Suppress the info, wether the logged output is written to file or only displayed in the outputchannel
        [parameter( Mandatory = $false )]
        [switch]$NoFileStatus,

        #Specifies that LogText is displayed as text in the debug-channel, not in the verbose-channel
        [parameter( Mandatory = $false,
            ParameterSetName = 'DebugOutput' )]
        [switch]$DebugOutput,

        #Specifies that LogText is displayed as text in the console window, not in the verbose-channel
        [parameter( Mandatory = $false,
            ParameterSetName = 'ConsoleOutput' )]
        [Alias('Visible')]
        [switch]$Console,

        #Specifies that LogText is displayed as (red) error message in the console window, not in the verbose-channel
        [parameter( Mandatory = $false,
            ParameterSetName = 'ErrorOutput' )]
        [switch]$Error,

        #Logs the LogText as warrning message to the console
        [parameter( Mandatory = $false,
            ParameterSetName = 'VerboseOutput' )]
        [parameter( Mandatory = $false,
            ParameterSetName = 'DebugOutput' )]
        [parameter( Mandatory = $false,
            ParameterSetName = 'ConsoleOutput' )]
        [switch]$Warning
    )

    begin {
        switch ($LogType) {
            'Warning' { $Type = '[WARNING] ' }
            'Info' { $Type = '[INFO ] ' }
            'Query' { $Type = '[QUERY ] ' }
            'Set' { $Type = '[SET ] ' }
            'Error' { $Type = '[ERROR ] ' }
            Default { $Type = '[INFO ] ' }
        }

        if ($logScope) { $LogScope = "[$($LogScope.ToUpper())] " }
        if ($NoFileStatus) { $status = '' } else { $status = "[NOFILE] " }
        if ($NoTimeStamp) { $logDate = '' } else { $logDate = "[$(Get-Date -Format "yyyy-MM-dd HH:mm:ss")] " }

        #turn of confimation for debug actions
        If (($DebugPreference -eq 'Inquire') -and ($PsCmdlet.ParameterSetName -eq 'DebugOutput')) {
            $DebugPreferenceOrg = $DebugPreference
            $DebugPreference = 'Continue'
        }
    }

    process {
        if ($LogFile) {
            foreach ($File in $LogFile) {
                "$($logDate)$($Type)$($LogScope)$($LogText)" | Out-File -FilePath $File -Append
            }
        }

        $message = "$($logDate)$($status)$($Type)$($LogScope)$($LogText)"
        switch ($PsCmdlet.ParameterSetName) {
            'VerboseOutput' {
                if ($Warning) { Write-Warning $message } else { write-verbose $message }
            }

            'DebugOutput' {
                if ($Warning) { Write-Debug "WARNING: $message" } else { Write-Debug $message }
            }

            'ConsoleOutput' {
                if ($Warning) {
                    Write-Host "WARNING: $message" -ForegroundColor $Host.PrivateData.WarningForegroundColor -BackgroundColor $Host.PrivateData.WarningBackgroundColor
                } else {
                    Write-Host $message
                }
            }

            'ErrorOutput' { Write-error $message }
        }
    }

    end {
        $DebugPreference = $DebugPreferenceOrg
        Remove-Variable message, logDate, status, Type, DebugPreferenceOrg -Force -ErrorAction Ignore -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
    }
}

function Connect-PRTGServer {
    <#
    .Synopsis
       Connect-PRTGServer
 
    .DESCRIPTION
       Connect to PRTG Server, creates global variables with connection data and the current sensor tree from PRTG Core Server.
       The global variables are used as default parameters in other PRTG-module cmdlets to interact with PRTG.
 
       Connect-PRTGServer needs to be run at first when starting to work.
 
    .NOTES
       Author: Andreas Bellstedt
 
       Created variables by the cmdlet:
            $script:PRTGServer
            $script:PRTGUser
            $script:PRTGPass
            $script:PRTGSensorTree (created through cmdlet Invoke-PRTGSensorTreeRefresh)
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       $ServerName = "PRTG.CORP.COMPANY.COM"
       $Credential = Get-Credential "prtgadmin"
 
       Connect-PRTGServer -Server $ServerName -protocol HTTPS -Credential $Credential
 
       #with output the connection data
       $connection = Connect-PRTGServer -Server $ServerName -protocol HTTPS -Credential $Credential -PassThru
 
    .EXAMPLE
       $ServerName = "PRTG.CORP.COMPANY.COM"
       $User = "prtgadmin"
       $Password = "SecretP@ssw0rd"
       Connect-PRTGServer -Server $ServerName -protocol HTTPS -User $User -PlainTextPassword $Password -Force
 
       #with output the connection data
       $connection = Connect-PRTGServer -Server $servername -protocol HTTPS -User $user -PlainTextPassword $pass -Force -PassThru
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    [CmdletBinding(
        DefaultParameterSetName = 'Credential',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([XML])]
    Param(
        # Url for PRTG Server
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_ -match '//') { $false }else { $true } })]
        [String]
        $Server,

        # Specifies if the connection is done with http or https
        [ValidateSet("HTTP", "HTTPS")]
        [ValidateNotNullOrEmpty()]
        [String]
        $protocol = "HTTPS",

        # The credentials to login to PRTG
        [Parameter(Position = 1, ParameterSetName = 'Credential')]
        [System.Management.Automation.PSCredential]
        $Credential,

        # The user name to login to PRTG
        [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'PlainTextPassword')]
        [Parameter(Position = 1, Mandatory = $true, ParameterSetName = 'Hash')]
        [ValidateNotNullOrEmpty()]
        [String]
        $User,

        # The password to login to PRTG
        [Parameter(Position = 2, Mandatory = $true, ParameterSetName = 'PlainTextPassword')]
        [String]
        $PlainTextPassword,

        # Enforcement switch to allow plain text parameters
        [Parameter(ParameterSetName = 'PlainTextPassword')]
        [Switch]
        $Force,

        # A PRTG login hash value
        [Parameter(Position = 2, Mandatory = $true, ParameterSetName = 'Hash')]
        [ValidateNotNullOrEmpty()]
        [String]
        $Hash,

        # Only login. No query of sensortree object
        [Alias('QuickConnect', 'NoSensorTree')]
        [Switch]
        $DoNotQuerySensorTree,

        # Output the sensortree object after login
        [Switch]
        $PassThru
    )

    begin {}

    process {
        switch ($protocol) {
            'HTTP' {
                $Prefix = 'http://'
                Write-Log -LogText "Unsecure $($protocol) connection detected. This is a security risk. Consider switch to HTTPS! Continue..." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -Warning
            }
            'HTTPS' {
                $Prefix = 'https://'
                Write-Log -LogText "Secure $($protocol) connection. OK." -LogType Info -LogScope $MyInvocation.MyCommand.Name -DebugOutput
            }
        }

        switch ($PsCmdlet.ParameterSetName) {
            'Credential' {
                if (-not $Credential) {
                    Write-Log -LogText "No credential specified! Credential is needed..." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -Warning -NoFileStatus
                    $Credential = Get-Credential -Message "Please specify logon cedentials for PRTG" -UserName $User
                }

                if (($credential.UserName.Split('\')).count -gt 1) {
                    $User = $credential.UserName.Split('\')[1]
                } else {
                    $User = $credential.UserName
                }

                $pass = $credential.GetNetworkCredential().Password
            }

            'PlainTextPassword' {
                if ($Force) {
                    $pass = $PlainTextPassword
                } else {
                    Write-Log -LogText "Plaintextpasswords without force parameter are not permitted!" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
                    return
                }
            }

            'Hash' {
                $Hash = Invoke-WebRequest -Uri "$Prefix$server/api/getpasshash.htm?username=$User&password=$Pass" -Verbose:$false -Debug:$false -ErrorAction Stop | Select-Object -ExpandProperty content
                Remove-Variable pass -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
            }
        }

        $script:PRTGServer = $Prefix + $server
        $script:PRTGUser = $User
        $script:PRTGPass = $Hash
        Write-Log -LogText "Connection to PRTG ($($script:PRTGServer)) as user $($script:PRTGUser)" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Console

        if (-not $DoNotQuerySensorTree) {
            Invoke-PRTGSensorTreeRefresh -Server $script:PRTGServer -User $script:PRTGUser -Pass $script:PRTGPass -Verbose:$false
        }

        if ($PassThru) {
            $Result = New-Object -TypeName psobject -Property @{
                Server         = $Prefix + $server
                User           = $User
                Pass           = $Hash
                Authentication = "&username=$User&passhash=$Hash"
            }
            $Result
        }
    }

    end {}
}


function Disconnect-PRTGServer {
    <#
    .Synopsis
       Disconnect-PRTGServer
 
    .DESCRIPTION
       Clears variables from memory:
            $script:PRTGServer
            $script:PRTGUser
            $script:PRTGPass
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Disconnect-PRTGServer
 
       Remove connection data, so it disconnects from PRTG Server.
    #>

    [CmdletBinding(
        SupportsShouldProcess = $false,
        ConfirmImpact = 'medium'
    )]
    Param(
        # Force to disconnect and suppress errors
        [Switch]
        $Force
    )

    if ($Force) { $ErrorAction = "SilentlyContinue" } else { $ErrorAction = "Continue" }

    Write-Log -LogText "Removing PRTG variables from memory" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
    "PRTGServer", "PRTGUser", "PRTGPass", "PRTGSensorTree" | ForEach-Object {
        Get-Variable $_ -Scope global -ErrorAction $ErrorAction | Remove-Variable -Scope global -Force -ErrorAction $ErrorAction -Verbose:$false -Debug:$false -WhatIf:$false
    }
}


function Get-PRTGSensorTree {
    <#
    .Synopsis
       Get-PRTGSensorTree
 
    .DESCRIPTION
       Return the current sensortree from PRTG Server
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGSensorTree
 
       Query the sensortree for caching prtg current object configuration.
 
    .EXAMPLE
       Get-PRTGSensorTree -Server "https://prtg.corp.customer.com" -User "prtgadmin" -Pass "1111111"
 
       Query the sensortree with custom credentials for caching prtg current object configuration.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default', SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    Param(
        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ if ( ($_.StartsWith("http")) ) { $true } else { $false } })]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )
    $body = @{
        username = $User
        passhash = $Pass
    }

    Write-Log -LogText "Getting PRTG SensorTree from PRTG Server $($Server)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
    [xml]$Result = Invoke-RestMethod -Uri "$Server/api/table.xml?content=sensortree" -Body $body -ErrorAction Stop -Verbose:$false

    $Result.pstypenames.Insert(0, "PRTG.SensorTree")
    $Result.pstypenames.Insert(1, "PRTG")

    return $Result
}


function Invoke-PRTGSensorTreeRefresh {
    <#
    .Synopsis
       Invoke-PRTGSensorTreeRefresh
 
    .DESCRIPTION
       Refreshes sensortree information from prtg server
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Invoke-PRTGSensorTreeRefresh
 
       Refreshes the sensortree for caching current prtg current object configuration.
 
    .EXAMPLE
       Invoke-PRTGSensorTreeRefresh -Server "https://prtg.corp.customer.com" -User "prtgadmin" -Pass "111111"
 
       Refreshes the sensortree with custom credentials for caching current prtg current object configuration.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'medium'
    )]
    Param(
        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ if ( ($_.StartsWith("http")) ) { $true } else { $false } })]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # Output the sensor tree
        [Switch]
        $PassThru
    )

    Write-Log -LogText "Refresh PRTG SensorTree in Memory" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
    $Result = Get-PRTGSensorTree -Server $Server -User $User -Pass $Pass -Verbose:$false
    $script:PRTGSensorTree = $Result

    if ($PassThru) { $Result }
}


function Compare-PRTGDeviceSensorsFromTemplateTAG {
    <#
    .Synopsis
       Compare-PRTGDeviceSensorsFromTemplateTAG
 
    .DESCRIPTION
       Compares all sensors on a device by all the tags on the device against a (template) object.
       In default all tags starting with "Template_" are used for comparing.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Compare-PRTGDeviceSensorsFromTemplateTAG -DeviceID 200 -TemplateBaseID 100
 
       Invokes comparisan of device with ID 200 against template device with ID 100
 
    .EXAMPLE
       Compare-PRTGDeviceSensorsFromTemplateTAG -DeviceID 200 -TemplateBaseID 100 -TemplateTAGNameIdentifier "MyPersonalTemplate_"
 
       Invokes comparisan of device with ID 200 against template device with ID 100 and use "MyPersonalTemplate_" as identifier for templates
 
    .EXAMPLE
       Get-PRTGDevice -Name "MyDevice" | Compare-PRTGDeviceSensorsFromTemplateTAG -TemplateBaseID (Get-PRTGProbe -Name "MyTemplateProbe").ObjID -IncludeEqual
 
        Invokes comparisan of "MyDevice" against all template devices beneath MyTemplateProbe
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the object to copy
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 })]
        [Alias('objID', 'ID', 'ObjectId')]
        [int]
        $DeviceID,

        # Base id of the template
        [ValidateNotNullOrEmpty()]
        [int]
        $TemplateBaseID = 1,

        # Filter text identifier for template tags in a device
        [ValidateNotNullOrEmpty()]
        [String]
        $TemplateTAGNameIdentifier = "Template_",

        # Compare properties inside a sensor as well as the existence inside the template
        [switch]
        $ComparePropertiesInObject,

        # Output objects that meet the template, as well as diffs to template
        [switch]
        $IncludeEqual,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        Write-Log -LogText "Getting device to validate with object ID $($DeviceID)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
        $DevicesToValidate = Get-PRTGDevice -ObjectId $DeviceID -SensorTree $SensorTree

        Write-Log -LogText "Getting role summary table from templatebase object ID $($TemplateBaseID)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
        $TemplateTAGSummary = Show-PRTGTemplateSummaryFromObjectTAG -TemplateBaseID $TemplateBaseID -TemplateTAGNameIdentifier $TemplateTAGNameIdentifier -SensorTree $SensorTree

        foreach ($Device in $DevicesToValidate) {
            Write-Log -LogText "Getting roles summary table for object ""$($device.name)"" (objID $($device.ObjID))" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            $DeviceTAGSummary = Show-PRTGTemplateSummaryFromObjectTAG -TemplateBaseID $Device.ObjID -TemplateTAGNameIdentifier $TemplateTAGNameIdentifier -SensorTree $SensorTree -IncludeNonMatching

            $result = @()
            foreach ($DeviceTAGSummaryItem in $DeviceTAGSummary) {
                if ($DeviceTAGSummaryItem.rolename) {
                    #if item is a "named" role
                    $Reference = ($DeviceTAGSummaryItem).sensor
                    $Difference = ($TemplateTAGSummary | Where-Object rolename -eq "$($DeviceTAGSummaryItem.RoleName)").sensor
                    if ($Reference -and $Difference) {
                        $ResultItem = Compare-Object -ReferenceObject $Reference -DifferenceObject $Difference -Property Name -PassThru -IncludeEqual

                        if ($ComparePropertiesInObject) {
                            #if comparision on objectdetails/-properties is requested -> compare properties against template
                            foreach ($SensorItem in ($ResultItem | Where-Object SideIndicator -eq "==")) {
                                #get the reference sensor
                                $ReferenceSensor = $Reference | Where-Object name -like $SensorItem.name

                                #compare properties on sensor against template
                                $differentProperty = Compare-ObjectProperty -ReferenceObject $SensorItem -DifferenceObject $ReferenceSensor -PropertyFilter "sensortype", "priority", "sensorkind", "interval", "tags", "Type", "IntervalText", "name"
                                if ($differentProperty) {
                                    $SensorItem.SideIndicator += "!"
                                    Add-Member -InputObject $SensorItem -MemberType NoteProperty -Force -Name "PropertyDifferenceReport" -Value ( [string]::Join(", ", ($differentProperty | ForEach-Object { "Difference on property $($_.property)=""$($_.Value)"" on $(if($_.SideIndicator -eq '<='){"device"}else{"template"})" })) )
                                } else {
                                    Add-Member -InputObject $SensorItem -MemberType NoteProperty -Force -Name "PropertyDifferenceReport" -Value $null
                                }
                            }
                        }

                        $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force -Name SideIndicatorStatus `
                                -Value (. {
                                    switch ($_.SideIndicator) {
                                        '<=' { "WARNING" }
                                        '=>' { "WARNING" }
                                        '==!' { "WARNING" }
                                        '==' { "OK" }
                                    }
                                })
                        }

                        $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force -Name "SideIndicatorDescription" `
                                -Value (. {
                                    switch ($_.SideIndicator) {
                                        '<=' { "In device but not in template" }
                                        '=>' { "In template but not in device" }
                                        '==!' { "Match in device and template, but difference in Properties! Look at PropertyDifferenceReport" }
                                        '==' { "Match in device and template" }
                                    }
                                })
                        }
                    } else {
                        #no sensors in device or no sensors in template
                        if (-not $Reference -and $Difference) {
                            #no sensors in device but some sensors in template
                            $ResultItem = $Difference
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicator"            -Value "=>" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorStatus"      -Value "WARNING" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorDescription" -Value "In template but not in device" }
                        } elseif ($Reference -and -not $Difference) {
                            #no sensors in template but some sensors in device
                            $ResultItem = $Reference
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicator"            -Value "<=" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorStatus"      -Value "WARNING" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorDescription" -Value "In device but not in template" }
                        } elseif (-not $Reference -and -not $Difference) {
                            #no sensors in device and no sensors in template
                            $ResultItem = ""
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicator"            -Value "!!" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorStatus"      -Value "WARNING" }
                            $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorDescription" -Value "No objects found" }
                        }
                    }
                } else {
                    #if item is a "NonMatching"-object (without a name in RoleName property)
                    $ResultItem = $DeviceTAGSummaryItem.Sensor
                    $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicator"            -Value "!!" }
                    $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorStatus"      -Value "WARNING" }
                    $ResultItem | ForEach-Object { $_ | Add-Member -MemberType NoteProperty -Force  -Name "SideIndicatorDescription" -Value "Object not matching any template" }
                }

                $ResultItem | ForEach-Object { if ($_.pstypenames[0] -ne "PRTG.Object.Compare") { $_.pstypenames.Insert(0, "PRTG.Object.Compare") } }

                $result += $ResultItem
            }

            if (-not $IncludeEqual) {
                $result = $result | Where-Object SideIndicator -ne '=='
            }

            $result
        }
    }

    end {}
}

function New-PRTGDefaultFolderStructureToProbe {
    <#
    .Synopsis
       New-PRTGDefaultFolderStructureToProbe
 
    .DESCRIPTION
       Copy a new folder/group structure to a destination
       Primary intension is to copy a tempalte folderstructure to a new probe
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       New-PRTGDefaultFolderStructureToProbe
 
       Required values will be queried by gridview-selection
 
    .EXAMPLE
       New-PRTGDefaultFolderStructureToProbe -TemplateFolderStructureID (Get-PRTGObject -Name "Template_group_name") -ProbeID (Get-PRTGProbes | Out-GridView -Title "Please select destination probe" -OutputMode Single)
 
       Creates group (folder) structure in a probe from "Template_group_name".
       All groups beneath the object "Template_group_name" will be copied to the probe selected by the Out-GridView cmdlet
 
    .EXAMPLE
       New-PRTGDefaultFolderStructureToProbe -TemplateFolderStructureID (Get-PRTGGroup -Name "MyTemplateGroup").objId -ProbeID (Get-PRTGProbes "NewProbe").ObjId
 
       Copy group structure beneath group "MyTemplateGroup" to the probe "NewProbe"
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    Param(
        # ID of the group that contains the structure to be copied to the destination probe
        [ValidateNotNullOrEmpty()]
        [int]
        $TemplateFolderStructureID = (Get-PRTGObject -Name "Groups for new customer" -Type group -SensorTree $script:PRTGSensorTree -Verbose:$false | Select-Object -ExpandProperty ObjID),

        # ID of the destination probe
        [ValidateNotNullOrEmpty()]
        [int]
        $ProbeID = (Get-PRTGProbe -SensorTree $script:PRTGSensorTree -Verbose:$false | Sort-Object fullname | Select-Object Name, objID | Out-GridView -Title "Please select destination probe" -OutputMode Single | Select-Object -ExpandProperty ObjID),

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ( ($_.StartsWith("http")) ) { $true }else { $false } })]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    $logscope = $MyInvocation.MyCommand.Name

    #Get tempalte to copy
    $TemplateFolderStructure = Get-PRTGObject -ID $TemplateFolderStructureID -Type group -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
    if (-not $TemplateFolderStructure.group) {
        Write-Log -LogText "Template folder not found or no groups under structure." -LogType Error -LogScope $logscope -NoFileStatus -Error
        return
    }

    #Get target object
    $ProbeNewCustomer = Get-PRTGObject -ID $ProbeID -Type probenode -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
    if (-not $ProbeNewCustomer) {
        Write-Log -LogText "No Probe specified." -LogType Error -LogScope $logscope -NoFileStatus -Error
        return
    }

    $count = 0
    $TotalCount = $TemplateFolderStructure.group.count
    $Copied = @()
    foreach ($item in $TemplateFolderStructure.group) {
        $count++
        Write-Progress -Activity "Copy data structure" -Status "Progress: $count of $($TotalCount)" -PercentComplete ($count / $TotalCount * 100)
        if ($pscmdlet.ShouldProcess($ProbeNewCustomer.name, "Deploy ""$($item.name)")) {
            Write-Log -LogText "Deploy objID $($item.ID[0]) ""$($item.name)"" to objID $($ProbeNewCustomer.objID) ""$($ProbeNewCustomer.name)""" -LogType Set -LogScope $logscope -NoFileStatus
            Remove-Variable ErrorEvent -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false -Confirm:$false
            try {
                $CopyObject = Copy-PRTGObject -ObjectId $item.ID[0] -TargetID $ProbeNewCustomer.ObjID -Name $item.name -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop
                Enable-PRTGObject -ObjectId $CopyObject.objid -Force -NoWaitOnStatus -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop
                $Copied += $CopyObject
                Remove-Variable CopyObject -Scope script -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false -Confirm:$false
            } catch {
                Write-Log -LogText "Error occured while deploying new folder structure! $($_.exception.message)" -LogType Error -LogScope $logscope -Error -NoFileStatus
                return
            }
        }
        Remove-Variable item -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false -Confirm:$false
    }
    Write-Log -LogText "$($Copied.count) objects copied" -LogType Info -LogScope $logscope -NoFileStatus

    Write-Log -LogText "Refresh PRTG SensorTree" -LogType Query -LogScope $logscope -NoFileStatus
    $SensorTree = Invoke-PRTGSensorTreeRefresh -Server $Server -User $User -Pass $Pass -PassThru -Verbose:$false
}


function New-PRTGDeviceFromTemplate {
    <#
    .Synopsis
       New-PRTGDeviceFromTemplate
 
    .DESCRIPTION
       Creates a new device out of a template structure, where operatingsystems and operatingsystem roles are separates in different templates.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       New-PRTGDeviceFromTemplate -DeviceName "NewDevice"
 
       Will create new device "NewDevice".
       Template information will be queried by Out-GridView.
 
    .EXAMPLE
       New-PRTGDeviceFromTemplate -DeviceName "NewDevice" -HostName "NewDevice.ad.corp.com" -TemplateSystem (Get-PRTGDevice -Name "Template_MyServerOS").ObjId -TemplateRole (Get-PRTGDevice -Name "Template_FileServer").ObjId -Destination (Get-PRTGProbe "NewProbe").ObjId
 
       Create a new device "NewDevice" from specified Systems.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    Param(
        # The DisplayName of new system
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $DeviceName,

        # The actual hostname for new system. If not specified, the DeviceName will be used
        [ValidateNotNullOrEmpty()]
        [String]
        $Hostname,

        # The ID for the system that acts as a template
        [int]
        $TemplateSystem = (Get-PRTGObject -Name "Basic operatingsystem" -Recursive -Type device -SensorTree $script:PRTGSensorTree | Sort-Object Fullname | Select-Object fullname, objID | Out-GridView -Title "Please specify operatingsystem for new system" -OutputMode Single | Select-Object -ExpandProperty ObjID),

        # The ID(s) of the template roles to apply to new device
        [int[]]
        $TemplateRole = (Get-PRTGObject -Name "Specific roles" -Recursive -Type device -SensorTree $script:PRTGSensorTree | Sort-Object Fullname | Select-Object FullName, objID | Out-GridView -Title "Please select roles for new system" -OutputMode Multiple | Select-Object -ExpandProperty ObjID),

        # Filter for sensor names to be excluded from template
        [string[]]
        $TemplateSensorFilter = "",

        # The ID of the destination group
        [ValidateNotNullOrEmpty()]
        [int]
        $Destination = (Get-PRTGObject -Type group -SensorTree $script:PRTGSensorTree | Sort-Object Fullname | Select-Object FullName, objID | Out-GridView -Title "Please select destination for new system" -OutputMode Single  | Select-Object -ExpandProperty ObjID),

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ( ($_.StartsWith("http")) ) { $true } else { $false } })]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    [array]$CopyObjectCollection = @()
    if (-not $Hostname) { $Hostname = $DeviceName }
    [String]$TemplateTAGName = "Template_*"

    #check if device currently existis in the destination
    Write-Log -LogText "Check if device already existis in the destination" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
    $DestinationContent = Get-PRTGObject -ID $Destination -Recursive -SensorTree $SensorTree -Verbose:$false -ErrorAction SilentlyContinue | Where-Object Type -like "device"
    $Device = Get-PRTGObject -Name $DeviceName -Type device -SensorTree $SensorTree -Verbose:$false -ErrorAction SilentlyContinue
    if ($Device -and ($Device.name -in $DestinationContent.name)) {
        Write-Log -LogText "$DeviceName ist unter ""$((Get-PRTGObject -ID $Destination -SensorTree $SensorTree -Verbose:$false).fullname)"" bereits vorhanden!" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
        $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ""
        $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ""
        $choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)
        $caption = "Warning!"
        $message = "$DeviceName`nist unter `n$((Get-PRTGObject -ID $Destination -SensorTree $SensorTree -Verbose:$false).fullname)`nbereits vorhanden!`n`nSoll das Device wirklich doppelt angelegt werden?"
        $result = $Host.UI.PromptForChoice($caption, $message, $choices, 1)
        if ($result -eq 1) {
            Write-Log -LogText "Abbruch..." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
            return
        }
    }
    Remove-Variable result, message, caption, choices, no, yes, device, DestinationContent -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false

    #Start cloning
    # Get "OS"-Device
    Write-Log -LogText "Start cloning devicetemplate to destination ID $Destination" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
    $TemplateSystemDevice = Get-PRTGObject -ID $TemplateSystem -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
    if (-not $TemplateSystemDevice) { Write-Log -LogText "Template not found!" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error; return }

    #clone to destination group and set hostname
    if ($pscmdlet.ShouldProcess("Object with ID $($Destination)", "Deploy new device ""$($DeviceName)"" from template ""$($TemplateSystemDevice.name)""")) {
        $NewCustomerServer = Copy-PRTGObject -ObjectId $TemplateSystemDevice.ObjID -TargetID $Destination -Name $DeviceName -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
        if ($NewCustomerServer) {
            Set-PRTGObjectProperty -ObjectId $NewCustomerServer.ObjID -PropertyName host -PropertyValue $Hostname -Server $Server -User $User -Pass $Pass -Verbose:$false -ErrorAction Stop
        } else {
            Write-Log -LogText "Error after copy "
        }
        $CopyObjectCollection += $NewCustomerServer
    }

    #if there are roles specified, query sensors from role-templates and clone them to the new system
    if ($TemplateRole) {
        #Query roles and select the sensors
        Write-Log -LogText "Query role specific sensor(s) from $($TemplateRole.count) role template(s)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
        [array]$TemplateSensorsToCopy = Get-PRTGObject -ID $TemplateRole -Recursive -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop | Where-Object Type -like "sensor"
        Write-Log -LogText "Found $($TemplateSensorsToCopy.count) role specific sensor(s)" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus

        #Filtering out sensors excluded from deployment process
        if ($TemplateSensorFilter) {
            Write-Log -LogText "Filtering out role specific sensor(s) from (Filter: $([string]::Join(", ",$TemplateSensorFilter)))" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
            foreach ($filter in $TemplateSensorFilter) {
                $TemplateSensorsToCopy = $TemplateSensorsToCopy | Where-Object name -NotLike $filter
            }
            Write-Log -LogText "$($TemplateSensorsToCopy.count) role specific sensor(s) left to clone after filtering" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
        }

        #Start deployment of role sensors
        if ($TemplateSensorsToCopy) {
            Write-Log -LogText "Start deployment of $($TemplateSensorsToCopy.count) role specific sensor(s)" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
            $script:count = 0
            $script:TotalCount = $TemplateSensorsToCopy.count
            $ProgressID = 16364  #random choosen number to produce a new progress bar

            foreach ($item in $TemplateSensorsToCopy) {
                $script:count++
                Write-Progress -Activity "Copy role sensors" -Status "Progress: $script:count of $($script:TotalCount)" -PercentComplete ($script:count / $script:TotalCount * 100) -id $ProgressID
                if ($pscmdlet.ShouldProcess($NewCustomerServer.name, "Deploy ""$($item.name)")) {
                    Write-Log -LogText "Deploy ""$($item.name)"" to ""$($NewCustomerServer.name)""" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
                    Remove-Variable ErrorEvent -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
                    try {
                        #copy object
                        $SensorCopy = Copy-PRTGObject -ObjectId $item.ObjID -TargetID $NewCustomerServer.ObjID -Name $item.name -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop
                        Write-Log -LogText "New sensor copied (objID:$($SensorCopy.objid) name:""$($SensorCopy.name))""" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput

                        #enable object
                        Write-Log -LogText "Activate / unpause new sensor" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        Enable-PRTGObject -ObjectId $SensorCopy.objid -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Force -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop

                        #Add tags from template to new device
                        Write-Log -LogText "Setting tags from role template to ""$($NewCustomerServer.name)""" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        [Array]$DeviceTAG = Get-PRTGObjectTAG -ObjectID $item.ParentNode.id[0] -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop | Where-Object { $_ -like $TemplateTAGName }
                        [Array]$DeviceTAG += Get-PRTGObjectTAG -ObjectID $item.ObjID            -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop | Where-Object { $_ -like $TemplateTAGName }
                        if ($DeviceTAG) {
                            #$SensorTree = Get-PRTGSensorTree -Server $Server -User $User -Pass $Pass
                            #Write TAG to Device
                            Add-PRTGObjectTAG -ObjectId $NewCustomerServer.ObjID -TAGName $DeviceTAG -Force -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop
                            #Write TAG to Sensor
                            Add-PRTGObjectTAG -ObjectId $SensorCopy.ObjID        -TAGName $DeviceTAG -Force -Server $Server -User $User -Pass $Pass -SensorTree $SensorTree -Verbose:$false -ErrorVariable ErrorEvent -ErrorAction Stop
                        }

                        #status output
                        [array]$CopyObjectCollection += $SensorCopy
                        Write-Log -LogText "$($CopyObjectCollection.Count) sensors created until now." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
                        Remove-Variable x -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
                    } catch {
                        #Write-Log -LogText $ErrorEvent.Message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                    }
                }
                Write-Progress -Activity "Copy role sensors" -id $ProgressID -Completed
            }
        } else {
            Write-Log -LogText "No role specific sensors to deploy" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
        }
    } else {
        Write-Log -LogText "No role template selected" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
    }

    if ($pscmdlet.ShouldProcess($NewCustomerServer.name, "Resume from pause status")) {
        Write-Log -LogText "Resume ""$($NewCustomerServer.name)"" from pause status" -LogType set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
        Enable-PRTGObject -ObjectId $NewCustomerServer.ObjID -Server $Server -User $User -Pass $Pass -Force -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
        Invoke-PRTGObjectRefresh -ObjectId $NewCustomerServer.ObjID -Server $Server -User $User -Pass $Pass -Verbose:$false -ErrorAction Stop
    }

    if ($SensorTree) {
        Write-Log -LogText "Refresh SensorTree" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus
        Invoke-PRTGSensorTreeRefresh -Server $Server -User $User -Pass $Pass -Verbose:$false
    }

    return $CopyObjectCollection
}


function Show-PRTGTemplateSummaryFromObjectTAG {
    <#
    .Synopsis
        Show-PRTGTemplateRoles
 
    .DESCRIPTION
        Display a list of template roles found under a groups and devices under a prtg structure
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Show-PRTGTemplateSummaryFromObjectTAG
 
        Display list of tags witch began with "Template_" and can be found under PRTG Core Server object.
        This is the default set of parameters.
 
    .EXAMPLE
        Show-PRTGTemplateSummaryFromObjectTAG -TemplateBaseID 100
 
        Display a list of tags witch began with "Template_" and are based under the group or device with the object ID 100.
 
    .EXAMPLE
        Show-PRTGTemplateSummaryFromObjectTAG -TemplateBaseID 100 -TemplateTAGNameIdentifier "MyPersonalTemplate-"
 
        Display a list of tags witch began with "MyPersonalTemplate-" and are based under the group or device with the object ID 100.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the object to copy
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {$_ -gt 0})]
        [Alias('objID', 'ID', 'ObjectId')]
        [int]
        $TemplateBaseID = 1,

        # Filter value to identify template tags
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {$_ -notcontains ("*", "?")})]
        [string]
        $TemplateTAGNameIdentifier = "Template_",

        # Include matching as non matching objects
        [switch]
        $IncludeNonMatching,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        #Build Template Role object for comparing against devices
        $TemplateRoleDevices = Get-PRTGObject -ObjectID $TemplateBaseID -Recursive -Type group, device -SensorTree $SensorTree | Where-Object tags -Match ([regex]::Escape($TemplateTAGNameIdentifier))
        $TemplateRoleDevicesTypeGroup = $TemplateRoleDevices | Group-Object type -NoElement | Select-Object Count, Name, @{N = "Text"; E = { "$($_.count) $($_.Name.tolower())$(if($_.count -ne 1){"s"})"}}

        #Start to create new object
        if ($TemplateRoleDevices.tags) {
            [array]$TemplateRoles = $TemplateRoleDevices.tags.split(' ') | Where-Object { $_ -Match ([regex]::Escape($TemplateTAGNameIdentifier)) } | Sort-Object -Unique | ForEach-Object { New-Object -TypeName psobject -Property @{"RoleName" = $_ } }
            Write-Log -LogText "Found $($TemplateRoles.count) role$(if($TemplateRole.count -ne 1){"s"}) from $([string]::Join("and ",$TemplateRoleDevicesTypeGroup.Text)) in templatebase object ID $TemplateBaseID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
        } else {
            Write-Log -LogText "No template roles found in object ID $TemplateBaseID" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            return
        }

        foreach ($TemplateRole in $TemplateRoles) {
            Write-Log -LogText "Building object for ""$($TemplateRole.RoleName)"" from $([string]::Join("and ",$TemplateRoleDevicesTypeGroup.Text)) under templatebase object ID $TemplateBaseID" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput

            [array]$device = $TemplateRoleDevices | Where-Object {$_.tags.split(' ') -eq $TemplateRole.RoleName}
            Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name DeviceCount -Value ([array]($device | Where-Object objId -ne $TemplateBaseID)).count
            Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name Device      -Value ([array]($device | Where-Object objId -ne $TemplateBaseID))
            if ($device.sensor) {
                [array]$sensor = $device.sensor | Where-Object {$_.tags.split(' ') -eq $TemplateRole.RoleName}
                Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name SensorCount -Value $sensor.count
                Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name Sensor      -Value ([array](Set-TypesNamesToPRTGObject -PRTGObject $sensor))
            } else {
                Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name SensorCount -Value 0
                Add-Member -InputObject $TemplateRole -MemberType NoteProperty -Force -Name Sensor      -Value $null
            }
            Remove-Variable device, sensor -Force -ErrorAction Ignore -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
        }

        if ($IncludeNonMatching) {
            Write-Log -LogText "Searching for objects not matching TAG-identifier ""$($TemplateTAGNameIdentifier)"" in $([string]::Join("and ",$TemplateRoleDevicesTypeGroup.Text)) under templatebase object ID $TemplateBaseID" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            [array]$device = $TemplateRoleDevices | Where-Object {$_.tags -notmatch ([regex]::Escape($TemplateTAGNameIdentifier))}
            [array]$sensor = $TemplateRoleDevices.sensor | Where-Object {$_.tags -notmatch ([regex]::Escape($TemplateTAGNameIdentifier))}
            if ( ($device | Where-Object objId -ne $TemplateBaseID) -or ($sensor) ) {
                Write-Log -LogText "found non matching objects. Inserting an empty RoleName object." -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                $TemplateRoles += New-Object -TypeName psobject -Property @{
                    "RoleName"    = ""
                    "DeviceCount" = if ($device | Where-Object objId -ne $TemplateBaseID) { ([array]($device | Where-Object objId -ne $TemplateBaseID)).count } else { 0 }
                    "Device"      = if ($device | Where-Object objId -ne $TemplateBaseID) { ([array]($device | Where-Object objId -ne $TemplateBaseID))       } else { $null }
                    "SensorCount" = if ($sensor) { $sensor.Count } else { 0 }
                    "Sensor"      = if ($sensor) { [array]([array](Set-TypesNamesToPRTGObject -PRTGObject $sensor)) } else { $null }
                }
            }
            Remove-Variable device, sensor -Force -ErrorAction Ignore -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
        }

        $TemplateRoles
    }

    end {}
}

function Get-PRTGDevice {
    <#
    .Synopsis
       Get-PRTGDevice
 
    .DESCRIPTION
       Returns one or more devices from sensortree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGDevice
 
       Query all devices from the default sensortree (global variable after connect to PRTG server)
 
    .EXAMPLE
       Get-PRTGDevice -SensorTree $SensorTree
 
       Query devices by name from a non default sensortree
 
    .EXAMPLE
       Get-PRTGDevice -Name "Device01"
 
       Query devices by name
 
    .EXAMPLE
       Get-PRTGDevice -Name "Device01", "Device*"
 
       Multiple names are possible
 
    .EXAMPLE
       "Device01" | Get-PRTGDevice
 
       Piping is also possible
 
    .EXAMPLE
       Get-PRTGDevice -ObjectId 1
 
       Query devices by object ID
 
    .EXAMPLE
       1 | Get-PRTGDevice
 
       Piping is also possible
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the PRTG object
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ID', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 })]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the device
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Name', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Name,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {
        $queryParam = @{
            "Type" = "device"
            "SensorTree" = $SensorTree
            "Verbose" = $false
        }
    }

    process {
        $result = @()

        switch ($PsCmdlet.ParameterSetName) {
            'ID' {
                New-Variable -Name result -Force
                foreach ($item in $ObjectId) {
                    $result += Get-PRTGObject -ObjectID $item @queryParam
                }
            }

            'Name' {
                foreach ($item in $Name) {
                    $result += Get-PRTGObject -Name $item @queryParam
                }
            }

            Default {
                $result = Get-PRTGObject @queryParam
            }
        }

        $result
    }

    end {}
}


function Get-PRTGGroup {
    <#
    .Synopsis
       Get-PRTGGroup
 
    .DESCRIPTION
       Returns one or more groups from sensortree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGGroup
 
       Query all groups from the default sensortree (global variable after connect to PRTG server)
 
    .EXAMPLE
       Get-PRTGGroup -SensorTree $SensorTree
 
       Query groups by name from a non default sensortree
 
    .EXAMPLE
       Get-PRTGGroup -Name "Group01"
 
       Query groups by name
 
    .EXAMPLE
       Get-PRTGGroup -Name "Group01", "Group*"
 
       Multiple names are possible
 
    .EXAMPLE
        "Group01" | Get-PRTGGroup
 
        Piping is also possible
 
    .EXAMPLE
       Get-PRTGGroup -ObjectId 1
 
       Query groups by object ID
 
    .EXAMPLE
       1 | Get-PRTGGroup
 
       Piping is also possible
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the PRTG object
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ID', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 })]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the group
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Name', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Name,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {
        $queryParam = @{
            "Type" = "group"
            "SensorTree" = $SensorTree
            "Verbose" = $false
        }
    }

    process {
        $result = @()

        switch ($PsCmdlet.ParameterSetName) {
            'ID' {
                foreach ($item in $ObjectId) {
                    $result += Get-PRTGObject -ObjectID $item @queryParam
                }
            }

            'Name' {
                foreach ($item in $Name) {
                    $result += Get-PRTGObject -Name $item @queryParam
                }
            }

            Default {
                $result = Get-PRTGObject @queryParam
            }
        }

        $result
    }

    end {}
}


function Add-PRTGObjectTAG {
    <#
    .Synopsis
        Add-PRTGObjectTAG
 
    .DESCRIPTION
        Add a text to the tags property of an PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Add-PRTGObjectTAG -ObjectId 1 -TAGName "NewName"
 
        Add TAG "NewName" to object 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 })]
        [Alias('ObjID', 'ID')]
        [int]
        $ObjectId,

        # Name of the object's property to set
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $TAGName,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ( ($_.StartsWith("http")) ) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree,

        # skip errors if an tag is not present
        [Switch]
        $Force,

        # returns the changed object
        [Switch]
        $PassThru
    )

    Begin {}

    Process {
        foreach ($ID in $ObjectId) {
            $break = $false
            #Get the object
            Write-Log -LogText "Gather object tags from object ID $ID." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            try {
                $Object = Get-PRTGObject -ID $ID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
            } catch {
                Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                break
            }

            #Build and check TAG lists
            if ($Object.tags) {
                [array]$TAGListExisting = $Object.tags.Split(' ')
            }
            $TAGListToAdd = @()
            foreach ($TAG in $TAGName) {
                if ($TAG.Contains(' ')) {
                    Write-Log -LogText "The tag ""$($TAG)"" contains invalid space characters! Space characters will be removed." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
                    $TAG = $TAG.Replace(' ', '')
                }
                if ($TAG -in $TAGListExisting) {
                    if ($Force) {
                        Write-Log -LogText "Skipping tag ""$($TAG)"", because it is already set on object id $ID" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
                    } else {
                        Write-Log -LogText "Tag ""$($TAG)"" is already set on object id $ID" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        $break = $true
                        break
                    }
                } else {
                    $TAGListToAdd += $TAG
                }
            }
            if ($break) { break }
            if ($TAGListExisting) {
                $TAGListToSet = "$($TAGListExisting) $([string]::Join(' ',$TAGListToAdd))"
            } else {
                $TAGListToSet = [string]::Join(' ', $TAGListToAdd)
            }
            $TAGListToSet = $TAGListToSet.Trim()

            #set TAG list to PRTG object
            if ($TAGListToAdd) {
                $MessageText = "Add $($TAGListToAdd.count) $(if($TAGListToAdd.count -eq 1) {"tag"} else {"tags"}) ($([string]::Join(' ',$TAGListToAdd)))"
                if ($pscmdlet.ShouldProcess("objID $ID", $MessageText)) {
                    Write-Log -LogText $MessageText -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    try {
                        #Set in PRTG
                        Set-PRTGObjectProperty -ObjectId $ID -PropertyName tags -PropertyValue $TAGListToSet -Server $Server -User $User -Pass $Pass -ErrorAction Stop -Verbose:$false

                        #Set on object to return
                        $Object.tags = $TAGListToSet

                        #Set in SensorTree variable
                        $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/tags").InnerText = $TAGListToSet
                    } catch {
                        Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        if (-not $Force) { break }
                    }
                }
            } else {
                Write-Log -LogText "No tags to set. Skipping object ID $($Object.objId)" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            }

            #output the object
            if ($PassThru) { $Object }

            #clear up the variable mess
            Remove-Variable TAG, TAGListExisting, TAGListToAdd, TAGListToSet, Object, MessageText -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
        }
    }

    End {}
}


function Copy-PRTGObject {
    <#
    .Synopsis
       Copy-PRTGObject
 
    .DESCRIPTION
       Copy a PRTG Object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
       adopted from PSGallery Module "PSPRTG"
       Author: Sam-Martin
       Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Copy-PRTGObject -ObjectId 1 -TargetID 2 -Name "NewName"
 
       Duplicates object 1 to group with ID 2 with name "NewName".
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'Medium'
    )]
    Param(
        # ID of the object to copy
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('SourceID', 'objID')]
        [int]
        $ObjectID,

        # ID of the target parent object
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [int]
        $TargetID,

        # Name of the newly cloned object
        [string]
        $Name,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if (($_.StartsWith("http"))) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # Sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        $body = @{
            id       = $ObjectId
            name     = $Name
            targetid = $TargetID
            username = $User
            passhash = $Pass
        }

        # get source object from sensor tree
        try {
            $SourceObject = Get-PRTGObject -ObjectID $ObjectID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
            if (-not $Name) { $body.name = $SourceObject.name }
        } catch {
            Write-Log -LogText "Cannot find object to clone. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
            return
        }

        # get target object from sensor tree
        try {
            $TargetObject = Get-PRTGObject -ObjectID $TargetID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
        } catch {
            Write-Log -LogText "Cannot find target object. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
            return
        }

        if ($pscmdlet.ShouldProcess("objID $TargetID '$($TargetObject.Name)'", "Clone object id $($SourceObject.ObjID) as '$Name'")) {
            #Try to clone the object
            try {
                Write-Log -LogText "Try to clone $($SourceObject.Type) with objID $($SourceObject.ObjID) to target with objID $TargetID. New name of the $($SourceObject.Type) in the target: ""$Name"" ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                $Result = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/duplicateobject.htm" -Method Get -Body $body -Verbose:$false -Debug:$false
            } catch {
                Write-Log -LogText "Failed to clone object $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
                return
            }
            Write-Log -LogText "$($SourceObject.Type) cloned to targetID $TargetID. Try to recieve new object from prtg ($Server)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput

            # Pluralise the type. Needed for receiving the copied object by "type"
            $TypePlural = $SourceObject.Type + 's'
            # Fetch the ID of the object we just added
            [array]$result = (Receive-PRTGObject -numResults 100 -columns "objid,name,type,tags,active,status" -SortBy "objid" -content $TypePlural -Filters @{"filter_name" = $Name } -Server $Server -User $User -Pass $Pass -Verbose:$false).$TypePlural.item | Where-Object objid -ne $ObjectId

            #output the object if result contains an objectID
            if ($result.ObjID) {

                #check for duplicated results
                if ($Result.Count -gt 1) {
                    Write-Log -LogText "Recieve $($Result.Count) $TypePlural from prtg ($Server). Assume highest ID as the new $($SourceObject.Type)." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $result = $result | Sort-Object -Property objid -Descending | Select-Object -First 1
                } else {
                    Write-Log -LogText "Recieve $($Result.Count) $($SourceObject.Type) ($($Result.objid)) from prtg ($Server)." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                }

                $newChild = $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ObjectID)]").CloneNode($true)
                $newObjectInSensorTree = $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($TargetID)]").AppendChild( $newChild )
                $newObjectInSensorTree.SetAttribute("id", $Result.objID)
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($TargetID)]//*[id=$($ObjectID)]/name").InnerText = $Result.name
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($TargetID)]//*[id=$($ObjectID)]/url").InnerText = $newObjectInSensorTree.url.Split('=')[0] + '=' + $Result.objID
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($TargetID)]//*[id=$($ObjectID)]/id").InnerText = $Result.objID

                # Ouput result
                $Result = Get-PRTGObject -ObjectID $Result.objID -SensorTree $SensorTree -Verbose:$false
                $Result
            } else {
                #if result contains an empty item
                Write-Log -LogText "No items recieved after cloning! Unkown issue ($Server)." -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                Remove-Variable result -Force -ErrorAction SilentlyContinue -Verbose:$false -Debug:$false -WhatIf:$false
                return
            }
        }
    }

    end {}
}

function Disable-PRTGObject {
    <#
    .Synopsis
        Disable-PRTGObject
 
    .DESCRIPTION
        Pause an PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
        Author: Andreas Bellstedt
 
        adopted from PSGallery Module "PSPRTG"
        Author: Sam-Martin
        Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Disable-PRTGObject -ObjectId 1
 
        Disable object with ID 1 for unlimited time
        No message is used.
 
    .EXAMPLE
        Disable-PRTGObject -ObjectId 1 -Message "Done by User01"
 
        Disable object with ID 1 for unlimited time with specified message.
 
    .EXAMPLE
        Disable-PRTGObject -ObjectId 1 -Message "Done by User01" -Minutes 1
 
        Disable object with ID 1 for one minute with specified message.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to pause
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID')]
        [int[]]
        $ObjectId,

        # Message to associate with the pause event
        [string]
        $Message,

        # Length of time in minutes to pause the object, $null for indefinite
        [int]
        $Minutes = $null,

        # do action regardless of current status of sensor
        [Switch]
        $Force,

        # Not waiting for sensor status update in PRTG server (faster reply on large batch jobs)
        [Switch]
        $NoWaitOnStatus,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # Sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                action   = 0
                username = $User
                passhash = $Pass
            }
            if ($Minutes) { $body.Add("duration", $Minutes) }
            if ($Message) { $body.Add("pausemsg", $Message) }

            if ($pscmdlet.ShouldProcess("objID $Id", "Disable PRTG object")) {
                try {
                    if ($Minutes) {
                        Write-Log -LogText "Disable object ID $id for $Minutes minutes. $(if($Message){"Message:$Message "})($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    } else {
                        Write-Log -LogText "Permanent disable object ID $id. $(if($Message){"Message:$Message "})($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    }
                    $StatusBefore = Receive-PRTGObjectStatus -ObjectID $id -Server $Server -User $User -Pass $Pass -Verbose:$false
                    if ( ($StatusBefore.status_raw -notin (7, 8, 9, 12)) -or $Force ) {
                        $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/pause$(if($Minutes){"objectfor"}).htm" -Method Get -Body $body -Verbose:$false -Debug:$false -ErrorAction Stop
                    } else {
                        Write-Log -LogText "Object ID $id is already disabled" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        break
                    }

                    if ($NoWaitOnStatus) {
                        if ($Minutes) {
                            $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/status_raw").InnerText = 12
                        } else {
                            $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/status_raw").InnerText = 7
                        }
                    } else {
                        $break = $false
                        do {
                            $StatusAfter = Receive-PRTGObjectStatus -ObjectID $id -Server $Server -User $User -Pass $Pass -Verbose:$false
                            if ($StatusBefore.status_raw -ne $StatusAfter.status_raw) {
                                $break = $true
                            }
                            Start-Sleep -Seconds 1
                        } until ($break)

                        $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/status_raw").InnerText = $StatusAfter.status_raw
                    }
                } catch {
                    Write-Log -LogText "Failed to disable object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                    break
                }
            }
        }
    }

    end {}
}


function Enable-PRTGObject {
    <#
    .Synopsis
       Enable-PRTGObject
 
    .DESCRIPTION
        Enables an (paused) PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
        Author: Andreas Bellstedt
 
        adopted from PSGallery Module "PSPRTG"
        Author: Sam-Martin
        Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Enable-PRTGObject -ObjectId 1
 
        Enables object with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # do action regardless of current status of sensor
        [Switch]
        $Force,

        # Not waiting for sensor status update in PRTG server (faster reply on large batch jobs)
        [Switch]
        $NoWaitOnStatus,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # Sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                action   = 1
                username = $User
                passhash = $Pass
            }

            $StatusBefore = Receive-PRTGObjectStatus -ObjectID $id -Server $Server -User $User -Pass $Pass -Verbose:$false
            if (-not $StatusBefore) {
                Write-Log "Failed to current status for object ID $id. Skipping object" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                break
            }

            if ($pscmdlet.ShouldProcess("objID $Id", "Enable PRTG object")) {
                try {
                    #Enable in PRTG
                    Write-Log -LogText "Enable object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    if ( ($StatusBefore.status_raw -in (7, 8, 9, 12)) -or $Force ) {
                        $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/pause.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                    } else {
                        Write-Log -LogText "Object ID $id is already enabled" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        break
                    }
                } catch {
                    Write-Log -LogText "Failed to enable object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                    break
                }

                #Receive new status
                $SafeguardBreakCount = 15
                $count = 0
                if ($NoWaitOnStatus) { $break = $true } else { $break = $false }
                do {
                    $StatusAfter = Receive-PRTGObjectStatus -ObjectID $id -Server $Server -User $User -Pass $Pass -Verbose:$false
                    if ($StatusBefore.status_raw -ne $StatusAfter.status_raw) { $break = $true }
                    if ($count -ge $SafeguardBreakCount) {
                        Write-Log -LogText "Error receiving enable-status from object $id! break status query." -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        $break = $true
                    }
                    Start-Sleep -Seconds 1
                    $count ++
                } until($break)

                #Set in SensorTree variable
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/status_raw").InnerText = $StatusAfter.status_raw
            }
        }
    }

    end {}
}


function Find-PRTGObject {
    <#
    .Synopsis
       Find-PRTGObject
 
    .DESCRIPTION
       Find objects from sensortree by various criteria
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Find-PRTGObject -ByTAGName "Server"
       Find-PRTGObject -ByTAGName "Template_*" -IncludeInherited
 
       Find-PRTGObject -ByTAGName "Template_*", "Server"
 
    .EXAMPLE
       Find-PRTGObject -ByStatus "Warning"
       Find-PRTGObject -ByStatus 'Paused by User', "Down"
 
    .EXAMPLE
       Find-PRTGObject -BySensorType POP3
       Find-PRTGObject -BySensorType POP3, DNS
 
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # Name of TAG to query objects
        [Parameter(Mandatory = $true, ParameterSetName = 'ByTAGName')]
        [string[]]
        $ByTAGName,

        # Indicates the search is case sensitive
        [Parameter(ParameterSetName = 'ByTAGName')]
        [switch]
        $CaseSensitive,

        # Include inherited
        [Parameter(ParameterSetName = 'ByTAGName')]
        [switch]
        $IncludeInherited,

        # The Status to query on objects
        [Parameter(Mandatory = $true, ParameterSetName = 'ByStatus')]
        [ValidateSet("Unknown", "Scanning", "Up", "Warning", "Down", "No Probe", "Paused by User", "Paused by Dependency", "Paused by Schedule", "Unusual", "Not Licensed", "Paused Until")]
        [string[]]
        $ByStatus,

        # The type of sensor to query
        [Parameter(Mandatory = $true, ParameterSetName = 'BySensorType')]
        [ValidateSet('Cloud HTTP', 'Cloud Ping', 'Serverzustand', 'DNS', 'VMWare Hostserver Hardware-Zustand (SOAP)', 'VMware Hostserver Leistung (SOAP)', 'Exchange Sicherung (Powershell)', 'Exchange Datenbank (Powershell)', 'Exchange Datenbank DAG (Powershell)', 'Exchange Postfach (Powershell)', 'Exchange Nachrichtenwarteschlange (Powershell)', 'Programm/Skript', 'Programm/Skript (Erweitert)', 'Datei-Inhalt', 'Ordner', 'FTP', 'Green IT', 'HTTP', 'HTTP (Erweitert)', 'Hyper-V Freigegebenes Clustervolume', 'IMAP', 'Windows Updates Status (Powershell)', 'Leistungsindikator IIS Anwendungspool', 'Ping', 'POP3', 'Port', 'Zustand der Probe', 'Active Directory Replikationsfehler', 'Windows Druckwarteschlange', 'WSUS-Statistiken', 'RDP (Remote Desktop)', 'Freigaben-Speicherplatz', 'SMTP', 'SNMP Prozessorlast', 'SNMP (Benutzerdef.)', 'SNMP-Zeichenfolge', 'SNMP Dell EqualLogic Physikalischer Datenträger', 'SNMP Dell PowerEdge Physikalischer Datenträger', 'SNMP SonicWALL Systemzustand', 'SNMP Dell PowerEdge Systemzustand', 'SNMP Plattenplatz', 'SNMP-Bibliothek', 'SNMP Linux Durchschnittl. Last', 'SNMP Linux Speicherinfo', 'SNMP Linux Physikalischer Datenträger', 'SNMP Speicher', 'SNMP QNAP Logischer Datenträger', 'SNMP QNAP Physikalischer Datenträger', 'SNMP QNAP Systemzustand', 'SNMP RMON', 'SNMP-Datenverkehr', 'SNMP-Laufzeit', 'SNTP', 'SSL-Sicherheitsüberprüfung', 'SSL-Zertifikatssensor', 'Systemzustand', 'SNMP-Trap-Empfänger', 'VMware Virtual Machine (SOAP)', 'VMware Datastore (SOAP)', 'Ereignisprotokoll (Windows API)', 'WMI Sicherheits-Center', 'WMI Laufwerkskapazität (mehrf.)', 'WMI Ereignisprotokoll', 'WMI Exchange Transportwarteschlange', 'Hyper-V Virtuelle Maschine', 'Hyper-V Host Server', 'Hyper-V Virtuelles Speichergerät', 'Windows IIS-Anwendung', 'WMI Logischer Datenträger E/A BETA', 'WMI Arbeitsspeicher', 'WMI Netzwerkadapter', 'WMI Auslagerungsdatei', 'Windows Physikalischer Datenträger E/A BETA', 'Windows Prozess', 'Windows Prozessorlast', 'WMI Dienst', 'WMI Freigabe', 'WMI Microsoft SQL Server 2012', 'WMI Terminaldienste (Windows 2008+)', 'Windows Systemlaufzeit', 'WMI UTC-Zeit', 'WMI Wichtige Systemdaten (v2)', 'WMI Datenträger')]
        [String[]]
        $BySensorType,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {
        $StatusMapping = @{
            "Unknown"              = 1
            "Scanning"             = 2
            "Up"                   = 3
            "Warning"              = 4
            "Down"                 = 5
            "No Probe"             = 6
            "Paused by User"       = 7
            "Paused by Dependency" = 8
            "Paused by Schedule"   = 9
            "Unusual"              = 10
            "Not Licensed"         = 11
            "Paused Until"         = 12
        }
        <#
        $SensorTypeMapping = @{
            'Cloud HTTP' = 'cloudhttp'
            'Cloud Ping' = 'cloudping'
            'Serverzustand' = 'corestate'
            'DNS' = 'dns'
            'VMWare Hostserver Hardware-Zustand (SOAP)' = 'esxserverhealthsensorextern'
            'VMware Hostserver Leistung (SOAP)' = 'esxserversensorextern'
            'Exchange Sicherung (Powershell)' = 'exchangepsbackup'
            'Exchange Datenbank (Powershell)' = 'exchangepsdatabase'
            'Exchange Datenbank DAG (Powershell)' = 'exchangepsdatabasedag'
            'Exchange Postfach (Powershell)' = 'exchangepsmailbox'
            'Exchange Nachrichtenwarteschlange (Powershell)' = 'exchangepsmailqueue'
            'Programm/Skript' = 'exe'
            'Programm/Skript (Erweitert)' = 'exexml'
            'Datei-Inhalt' = 'filecontent'
            'Ordner' = 'folder'
            'FTP' = 'ftp'
            'Green IT' = 'green'
            'HTTP' = 'http'
            'HTTP (Erweitert)' = 'httpadvanced'
            'Hyper-V Freigegebenes Clustervolume' = 'hypervcsvdiskfree'
            'IMAP' = 'imap'
            'Windows Updates Status (Powershell)' = 'lastwindowsupdate'
            'Leistungsindikator IIS Anwendungspool' = 'pciisapppool'
            'Ping' = 'ping'
            'POP3' = 'pop3'
            'Port' = 'port'
            'Zustand der Probe' = 'probestate'
            'Active Directory Replikationsfehler' = 'ptfadsreplfailurexml'
            'Windows Druckwarteschlange' = 'ptfprintqueue'
            'WSUS-Statistiken' = 'ptfwsusstatistics'
            'RDP (Remote Desktop)' = 'remotedesktop'
            'Freigaben-Speicherplatz' = 'smbdiskspace'
            'SMTP' = 'smtp'
            'SNMP Prozessorlast' = 'snmpcpu'
            'SNMP (Benutzerdef.)' = 'snmpcustom'
            'SNMP-Zeichenfolge' = 'snmpcustomstring'
            'SNMP Dell EqualLogic Physikalischer Datenträger' = 'snmpdellequallogicphysicaldisk'
            'SNMP Dell PowerEdge Physikalischer Datenträger' = 'snmpdellphysicaldisk'
            'SNMP SonicWALL Systemzustand' = 'snmpdellsonicwallsystemhealth'
            'SNMP Dell PowerEdge Systemzustand' = 'snmpdellsystemhealth'
            'SNMP Plattenplatz' = 'snmpdiskfree'
            'SNMP-Bibliothek' = 'snmplibrary'
            'SNMP Linux Durchschnittl. Last' = 'snmplinuxloadavg'
            'SNMP Linux Speicherinfo' = 'snmplinuxmeminfo'
            'SNMP Linux Physikalischer Datenträger' = 'snmplinuxphysicaldisk'
            'SNMP Speicher' = 'snmpmemory'
            'SNMP QNAP Logischer Datenträger' = 'snmpqnaplogicaldisk'
            'SNMP QNAP Physikalischer Datenträger' = 'snmpqnapphysicaldisk'
            'SNMP QNAP Systemzustand' = 'snmpqnapsystemhealth'
            'SNMP RMON' = 'snmprmon'
            'SNMP-Datenverkehr' = 'snmptraffic'
            'SNMP-Laufzeit' = 'snmpuptime'
            'SNTP' = 'sntp'
            'SSL-Sicherheitsüberprüfung' = 'ssl'
            'SSL-Zertifikatssensor' = 'sslcertificate'
            'Systemzustand' = 'systemstate'
            'SNMP-Trap-Empfänger' = 'udptrap'
            'VMware Virtual Machine (SOAP)' = 'vcenterserverextern'
            'VMware Datastore (SOAP)' = 'vmwaredatastoreextern'
            'Ereignisprotokoll (Windows API)' = 'winapieventlog'
            'WMI Sicherheits-Center' = 'wmiantivirus'
            'WMI Laufwerkskapazität (mehrf.)' = 'wmidiskspace'
            'WMI Ereignisprotokoll' = 'wmieventlog'
            'WMI Exchange Transportwarteschlange' = 'wmiexchangetransportqueues'
            'Hyper-V Virtuelle Maschine' = 'wmihyperv'
            'Hyper-V Host Server' = 'wmihypervserver'
            'Hyper-V Virtuelles Speichergerät' = 'wmihypervvirtualstoragedevice'
            'Windows IIS-Anwendung' = 'wmiiis'
            'WMI Logischer Datenträger E/A BETA' = 'wmilogicaldiskv2'
            'WMI Arbeitsspeicher' = 'wmimemory'
            'WMI Netzwerkadapter' = 'wminetwork'
            'WMI Auslagerungsdatei' = 'wmipagefile'
            'Windows Physikalischer Datenträger E/A BETA' = 'wmiphysicaldiskv2'
            'Windows Prozess' = 'wmiprocess'
            'Windows Prozessorlast' = 'wmiprocessor'
            'WMI Dienst' = 'wmiservice'
            'WMI Freigabe' = 'wmishare'
            'WMI Microsoft SQL Server 2012' = 'wmisqlserver2012'
            'WMI Terminaldienste (Windows 2008+)' = 'wmiterminalservices2008'
            'Windows Systemlaufzeit' = 'wmiuptime'
            'WMI UTC-Zeit' = 'wmiutctime'
            'WMI Wichtige Systemdaten (v2)' = 'wmivitalsystemdata'
            'WMI Datenträger' = 'wmivolume'
        }
        #>

    }

    process {
        switch ($PsCmdlet.ParameterSetName) {
            'ByTAGName' {
                if ($CaseSensitive -and (-not $IncludeInherited)) {
                    foreach ($TagName in $ByTAGName) {
                        Write-Log -LogText "Search casesensitive only in 'tags'-property for $TagName in SensorTree" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        New-Variable -Name result -Force -Confirm:$false -Debug:$false -Verbose:$false -WhatIf:$false
                        $result = $SensorTree.SelectNodes("/prtg/sensortree/nodes/group//*[contains(tags,'$($TagName.Replace('*',''))')]")
                        Set-TypesNamesToPRTGObject -PRTGObject $result
                    }
                } else {
                    Write-Log -LogText "Search method: caseinsensitive" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    New-Variable -Name objects -Force -Confirm:$false -Debug:$false -Verbose:$false -WhatIf:$false
                    $Objects = Get-PRTGObject -SensorTree $SensorTree -Verbose:$false

                    foreach ($Object in $Objects) {
                        foreach ($TagName in $ByTAGName) {
                            if ($IncludeInherited) {
                                Write-Log -LogText "Search caseinsensitive for $TagName in 'tagsAll'-property" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                                if ($Object.tagsAll) {
                                    if ($Object.tagsAll.split(' ') -like $TagName) {
                                        $Object
                                    }
                                }
                            } else {
                                Write-Log -LogText "Search caseinsensitive for $TagName in 'tags'-property" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                                if ($Object.tags) {
                                    if ($Object.tags.split(' ') -like $TagName) {
                                        $Object
                                    }
                                }
                            }
                        }
                    }
                }
            }

            'ByStatus' {
                foreach ($Status in $ByStatus) {
                    Write-Log -LogText "Searching for objects by staus $Status" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    New-Variable -Name result -Force -Confirm:$false -Debug:$false -Verbose:$false -WhatIf:$false
                    $result = $SensorTree.SelectNodes("/prtg/sensortree/nodes/group//*[status_raw=$($StatusMapping."$Status")]")
                    Set-TypesNamesToPRTGObject -PRTGObject $result
                }
            }

            'BySensorType' {
                foreach ($SensorType in $BySensorType) {
                    Write-Log -LogText "Searching for objects by type $SensorType" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    New-Variable -Name result -Force -Confirm:$false -Debug:$false -Verbose:$false -WhatIf:$false
                    $result = $SensorTree.SelectNodes("/prtg/sensortree/nodes/group//*[sensortype='$("$SensorType")']")
                    Set-TypesNamesToPRTGObject -PRTGObject $result
                }
            }
        }
    }

    end {}
}


function Get-PRTGObject {
    <#
    .Synopsis
       Get-PRTGObject
 
    .DESCRIPTION
       Returns one more multiple types of objects from sensortree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGObject
 
       Query all objects from the default sensortree (global variable after connect to PRTG server)
 
    .EXAMPLE
       Get-PRTGObject -SensorTree $SensorTree
 
       Query objects by name from a non default sensortree
 
    .EXAMPLE
       Get-PRTGObject -Name "Object01"
 
       Query objects by name
 
    .EXAMPLE
       Get-PRTGObject -Name "Object01", "Object*" -Recursive
 
       Query objects by name with all subobjects
 
    .EXAMPLE
       Get-PRTGObject -Name "Object01", "Object*" -Type 'probenode', 'group', 'device', 'sensor'
 
       Query only selected type of objects by name
 
    .EXAMPLE
       PS C:\>Get-PRTGObject -Name "Object01", "Object*" -Type 'probenode', 'group', 'device', 'sensor' -Recursive
 
       Query only selected type of objects by name with all subobjects
 
       PS C:\>Get-PRTGObject -Name "Object01", "Object*" -SensorTree $SensorTree
 
       Query objects by name from a non default sensortree
 
       PS C:\>"Object01" | PRTGObject
 
       Piping is also possible
 
    .EXAMPLE
       PS C:\>Get-PRTGObject -ObjectID 1
 
       Query objects by object ID
 
       PS C:\>Get-PRTGObject -ObjID 1 , 100
       PS C:\>Get-PRTGObject -ID 1, 100
 
       all the parameter combination from the example above are also possible
 
       PS C:\>1 | Get-PRTGObject
       Piping is also possible
 
    .EXAMPLE
       PS C:\>Get-PRTGObject -FilterXPath "/prtg/sensortree/nodes/group//*[id='1']"
 
       for people who know what they do... :-)
       query objects directly by XPatch filter string
 
       PS C:\>Get-PRTGObject -FilterXPath "/prtg/sensortree/nodes/group//probenode" -SensorTree $SensorTree
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the PRTG object
        [Parameter(ParameterSetName = 'ID', Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Parameter(ParameterSetName = 'ReturnAll', Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('objID', 'ID')]
        [int[]]
        $ObjectID,

        # The name of the object
        [Parameter(ParameterSetName = 'Name', Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Name,

        # The fullname of the object
        [Parameter(ParameterSetName = 'FullName', Position = 0, Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $FullName,

        # XPath filter expression for advanced usage
        [Parameter(ParameterSetName = 'XPath', Position = 0, Mandatory = $true)]
        [String[]]
        $FilterXPath,

        # Filter of types
        [Parameter(ParameterSetName = 'ReturnAll')]
        [Parameter(ParameterSetName = 'ID')]
        [Parameter(ParameterSetName = 'Name')]
        [Parameter(ParameterSetName = 'FullName')]
        [ValidateSet('group', 'device', 'sensor', 'probenode')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $Type = ('group', 'device', 'sensor', 'probenode'),

        # Parse recursivly through the groups
        [Parameter(ParameterSetName = 'ReturnAll')]
        [Parameter(ParameterSetName = 'ID')]
        [Parameter(ParameterSetName = 'Name')]
        [Parameter(ParameterSetName = 'FullName')]
        [switch]
        $Recursive,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    Begin {}

    Process {
        [Array]$result = @()
        switch ($PsCmdlet.ParameterSetName) {
            'ID' { $ParameterSet = 'ID' }
            'Name' { $ParameterSet = 'Name' }
            'Fullname' { $ParameterSet = 'Fullname' }
            'XPath' { $ParameterSet = 'XPath' }
            { $_ -eq 'ReturnAll' -and $ObjectID } { $ParameterSet = 'ID' }
            Default { $ParameterSet = 'ReturnAll' }
        }
        #Write-Host "$ParameterSet - $ObjectID" -ForegroundColor Green

        switch ($ParameterSet) {
            'ID' {
                foreach ($ID in $ObjectId) {
                    Write-Log -LogText "Query logic is:$($ParameterSet). Going to find the object by ID $($ID) value." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    [string]$BasePath = "/prtg/sensortree/nodes/group//*[id=$($ID)]"
                    $SeachString = $BasePath
                    if ($Recursive) {
                        foreach ($item in $Type) {
                            Write-Log -LogText "Recursice switch detected. Compiling recursive searchstring for $($item)s unter object $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                            $SeachString = $SeachString + ' | ' + $BasePath + "//" + $item
                        }
                    }
                    Write-Log -LogText "Searchstring for query on sensortree: $SeachString." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $result += $SensorTree.SelectNodes($SeachString)
                    if ($type.count -lt 4) { $result = $result | Where-Object LocalName -In $type }
                    Write-Log -LogText "Found $($result.Count) $([string]::Join('s,',$Type))s in sensortree." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    Remove-Variable item, SeachString, BasePath -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
                }
            }

            'Name' {
                foreach ($NameItem in $Name) {
                    Write-Log -LogText "Query logic is:$($ParameterSet). Going to find the object by Name ""$($Name)"" value." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    #assume find object directly no filtering after xpath needed
                    $FilterAfterQuery = $false
                    [string]$BasePath = "/prtg/sensortree/nodes/group//*[name='$($NameItem)']"
                    if ($NameItem.contains('*') -and $NameItem.StartsWith('*') -and $NameItem.EndsWith('*') -and (-not $NameItem.Trim('*').contains('*'))) {
                        #$NameItem is something like "*machine*" -> xpath with "contains" can be used, no filtering after xpath needed
                        [string]$BasePath = "/prtg/sensortree/nodes/group//*[contains(name,'$($NameItem.Replace('*',''))')]"
                    } elseif ($NameItem.contains('*')) {
                        #$NameItem is something like "*machine*01*" or "machine*01" -> filter after xpath is needed
                        [string]$BasePath = '/prtg/sensortree/nodes/group'
                        $FilterAfterQuery = $true
                        Write-Log -LogText "Switch on FilterAfterQuery" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    }
                    $SeachString = $BasePath
                    if ($Recursive -or $FilterAfterQuery) {
                        foreach ($item in $Type) {
                            Write-Log -LogText "Recursice switch detected. Compiling recursive searchstring for $($item)s." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                            $SeachString = $SeachString + ' | ' + $BasePath + "//" + $item
                        }
                    }
                    Write-Log -LogText "Searchstring for query on sensortree: $SeachString." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    if ($FilterAfterQuery -and (-not $Recursive)) {
                        $result += $SensorTree.SelectNodes($SeachString) | Where-Object Name -Like $NameItem
                    } elseif ($FilterAfterQuery -and $Recursive) {
                        $group = $SensorTree.SelectNodes($SeachString) | Where-Object Name -Like $NameItem | Group-Object type
                        $subResult = @()
                        foreach ($g in $group) {
                            switch ($g.name) {
                                'sensor' { $result += $g.group }
                                Default {
                                    [int]$i = 0
                                    [int]$iComplete = $g.Group.count
                                    foreach ($h in $g.Group) {
                                        $i++
                                        Write-Progress -Activity "Recursive filtering..." -PercentComplete ($i / $iComplete * 100)
                                        $subResult += Get-PRTGObject -ObjectID $h.ObjID -Recursive -SensorTree $SensorTree -Verbose:$false
                                    }
                                }
                            }
                        }
                        $result += $subResult
                    } else {
                        $result += $SensorTree.SelectNodes($SeachString)
                    }
                    if ($Type.count -lt 4) { $result = $result | Where-Object LocalName -in $Type }
                    Write-Log -LogText "Found $($result.Count) $([string]::Join('s,',$Type))s in sensortree." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    Remove-Variable item, SeachString, BasePath -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
                }
            }

            'Fullname' {
                foreach ($FullnameItem in $Fullname) {
                    if ($Recursive) { $FullnameItem = "*$FullnameItem*" }
                    Write-Log -LogText "Query logic is:$($ParameterSet). Going to find the object by FullName ""$($FullnameItem)"" value." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    [string]$BasePath = '/prtg/sensortree/nodes/group'
                    $Count = 0
                    foreach ($item in $Type) {
                        Write-Log -LogText "Compiling searchstring for $item" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        if ($Count -eq 0) {
                            $SeachString = $BasePath + "//" + $item
                        } else {
                            $SeachString = $SeachString + ' | ' + $BasePath + "//" + $item
                        }
                        $count ++
                    }
                    Write-Log -LogText "Final Searchstring for sensortree: $SeachString" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $objects = $SensorTree.SelectNodes($SeachString)

                    $objectsCollection = foreach ($item in $objects) {
                        $item.pstypenames.Insert(0, "PRTG.Object")
                        $item.pstypenames.Insert(1, "PRTG")
                        $item
                    }
                    Remove-Variable item -Force -ErrorAction Ignore

                    foreach ($search in $FullnameItem) {
                        $result += $objectsCollection | Where-Object fullname -Like $search
                    }
                    Write-Log -LogText "Found $($result.Count) $([string]::Join('s,',$Type))s in sensortree." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    Remove-Variable Count, item, SeachString, BasePath -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
                }
            }

            'XPath' {
                foreach ($XPath in $FilterXPath) {
                    Write-Log -LogText "Query logic is:$($ParameterSet). Returning all objects with filterstring: ""$($XPath)""" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $result = $SensorTree.SelectNodes($XPath)
                    Write-Log -LogText "Found $($result.Count) objects in sensortree." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                }
            }

            Default {
                Write-Log -LogText "Query logic is:$($ParameterSet). Returning all objects" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                [string]$BasePath = '/prtg/sensortree/nodes/group'
                $Count = 0
                foreach ($item in $Type) {
                    Write-Log -LogText "Compiling searchstring for $item" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    if ($Count -eq 0) {
                        $SeachString = $BasePath + "//" + $item
                    } else {
                        $SeachString = $SeachString + ' | ' + $BasePath + "//" + $item
                    }
                    $count ++
                }
                Write-Log -LogText "Final Searchstring for sensortree: $SeachString." -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                $result = $SensorTree.SelectNodes($SeachString)
                Write-Log -LogText "Found $($result.Count) $([string]::Join(',',$Type)) in sensortree." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                Remove-Variable Count, item, SeachString, BasePath -Force -ErrorAction Ignore -Verbose:$false -Debug:$false -WhatIf:$false
            }
        }

        if ($result) {
            if ($PsCmdlet.ParameterSetName -ne 'ReturnAll' -and $Type.count -gt 1) {
                $result = $result | select-object * -Unique
            }
            Set-TypesNamesToPRTGObject -PRTGObject $result
        } else {
            Write-Log -LogText "Error query object by $($PsCmdlet.ParameterSetName). No object found!" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
        }
    }

    End {}
}


function Get-PRTGObjectProperty {
    <#
    .Synopsis
       Get-PRTGObjectProperty
 
    .DESCRIPTION
       Get a specific property from an PRTG object out of the sensor tree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Get-PRTGObjectProperty -ObjectId 1 -PropertyName Name, tags
 
        Get name and tags from object with ID 1
 
    .EXAMPLE
        Get-PRTGObject -Name "Myobject" | Get-PRTGObjectProperty -Name Name, status
 
        Get name and status from object "Myobject"
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType([PSCustomObject])]
    Param(
        # ID of the object to pause/resume
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ $_ -gt 0 })]
        [Alias('objID', 'ID')]
        [int[]]
        $ObjectID,

        # Name of the object's property to get
        [Parameter(Position = 1, ParameterSetName = 'Name')]
        [Alias('Name')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $PropertyName,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    Begin {}

    Process {
        foreach ($ID in $ObjectID) {
            Write-Log -LogText "Get object details from object ID $ID." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            try {
                $Object = Get-PRTGObject -ID $ID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
            } catch {
                Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                return
            }

            $ObjectProperty = $Object | Get-Member -MemberType Property, NoteProperty | Select-Object -ExpandProperty Name
            $hash = @{}
            if ($PropertyName) {
                # Parameterset: Name
                foreach ($item in $PropertyName) {
                    $PropertiesToQuery = $ObjectProperty | Where-Object { $_ -like $item }
                    foreach ($PropertyItem in $PropertiesToQuery) {
                        if ($hash.$PropertyItem) {
                            Write-Log -LogText "Property $PropertyItem already existis! Skipping this one." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        } else {
                            Write-Log -LogText "Get property $PropertyItem from object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                            $hash.Add($PropertyItem, $object.$PropertyItem)
                        }
                    }
                }
                $result = New-Object -TypeName PSCustomObject -Property $hash
            } else {
                #Parameterset: ReturnAll
                Write-Log -LogText "Get all properties from object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                foreach ($PropertyItem in $ObjectProperty) {
                    $hash.Add($PropertyItem, $object.$PropertyItem)
                }
            }
            $result = New-Object -TypeName PSCustomObject -Property $hash

            Write-Log -LogText "Found $($result.count) $(if($result.count -eq 1){"property"}else{"properties"}) in object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            $result
        }
    }

    End {}
}


function Get-PRTGObjectTAG {
    <#
    .Synopsis
       Get-PRTGObjectTAG
 
    .DESCRIPTION
       Get the tags property from an PRTG object out of the sensor tree and returns a string array
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Get-PRTGObjectTAG -ObjectId 1
 
        Get ObjectTags from object with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    [OutputType( [String[]] )]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('objID', 'ID')]
        [int]
        $ObjectID,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {}

    process {
        #Get the object
        Write-Log -LogText "Get object tags from object ID $ObjectID." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
        try {
            $Object = Get-PRTGObject -ID $ObjectID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
        } catch {
            Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
            return
        }

        if ($Object.tags) {
            [Array]$result = $Object.tags.Split(' ')
        } else {
            Write-Log -LogText "No tags in object ""$($Object.name)"" (ID:$($ObjectID))" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
            return
        }

        Write-Log -LogText "Found $($result.count) $(if($result.count -eq 1){"tag"}else{"tags"}) in object ID $ObjectID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
        $result
    }

    end {}
}


function Invoke-PRTGObjectRefresh {
    <#
    .Synopsis
       Invoke-PRTGObjectRefresh
 
    .DESCRIPTION
       Enables an (paused) PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Invoke-PRTGObjectRefresh -ObjectId 1
 
       Refreshes objct with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to resume
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if (($_.StartsWith("http"))) {$true} else {$false} } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {
        $body = @{
            id       = 0
            username = $User
            passhash = $Pass
        }
    }

    Process {
        foreach ($id in $ObjectId) {
            $body.id = $id
            if ($pscmdlet.ShouldProcess("objID $Id", "Call scan now procedure on object")) {
                try {
                    Write-Log -LogText "Call scan now procedure on object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/scannow.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to Call scan now procedure on object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                }
            }
        }
    }

    End {}
}


function Move-PRTGObjectPosition {
    <#
    .Synopsis
       Move-PRTGObjectPosition
 
    .DESCRIPTION
       Moves an object in PRTG hierarchy
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Move-PRTGObject -ObjectId 1 -Direction up
 
        Move object with ID 1 one position up inside the group/structure
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Message to associate with the pause event
        [Parameter(Mandatory = $true)]
        [ValidateSet("up", "down", "top", "bottom")]
        [string]
        $Direction,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    begin {}

    process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                newpos   = $Direction
                username = $User
                passhash = $Pass
            }

            if ($pscmdlet.ShouldProcess("objID $Id", "Move $Direction")) {
                try {
                    Write-Log -LogText "Move object ID $id $Direction ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/setposition.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to move object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                }
            }
        }
    }

    end {}
}


function Receive-PRTGObject {
    <#
    .Synopsis
       Receive-PRTGObject
 
    .DESCRIPTION
       Query an object directly from PRTGserver and returns.
       Difference to Get-PRTGObject is, that "Get-PRTGObject" is working on a modfified sensortree variable in the memory and not on livedata from PRTGServer
 
    .NOTES
       Author: Andreas Bellstedt
 
       adopted from PSGallery Module "PSPRTG"
       Author: Sam-Martin
       Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       PS C:\>Receive-PRTGObject
 
       Receive devices live from PRTG server (not using the cached sensor tree info)
 
    .EXAMPLE
       PS C:\>Receive-PRTGObject -Content sensors
 
       Receive sensors live from PRTG server (not using the cached sensor tree info)
 
       #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'low'
    )]
    Param(
        # Number of maximal results
        [int]
        $NumResults = 99999,

        # Properties to query
        [string]
        $Columns = "objid,type,name,tags,active,host",

        # Type of device to query
        [ValidateSet("sensortree", "groups", "sensors", "devices", "tickets", "messages", "values", "channels", "reports", "storedreports", "ticketdata")]
        [string]
        $Content = "devices",

        # sorting the output
        [string]
        $SortBy = "objid",

        # Direction to sort the output
        [ValidateSet("Desc", "Asc")]
        [string]
        $SortDirection = "Desc",

        # Filter hashtable to filter out objects from the query
        [hashtable]
        $Filters,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    $SortDirectionPRTGStyle = if ($SortDirection -eq "Desc") { "-" }else { '' }
    $body = @{
        content  = $content;
        count    = $numResults;
        output   = "xml";
        columns  = $columns;
        sortby   = "$SortDirectionPRTGStyle$SortBy";
        username = $User
        passhash = $Pass
    }

    foreach ($FilterName in $Filters.keys) {
        $body.Add($FilterName, $Filters.$FilterName)
    }

    # Try to get the PRTG device tree
    try {
        $prtgDeviceTree = Invoke-RestMethod -UseBasicParsing -Uri "$Server/api/table.xml" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
    } catch {
        Write-Log -LogText "Failed to get PRTG Device tree $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
        return
    }

    $prtgDeviceTree
}


function Receive-PRTGObjectDetail {
    <#
    .Synopsis
        Receive-PRTGObjectDetail
 
    .DESCRIPTION
        Query status information for an object directly from PRTGserver.
        (function not working on sensortree variable in memory)
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        PS C:\>Receive-PRTGObjectDetail -ObjectId 1
        PS C:\>Receive-PRTGObjectDetail -ID 1
 
        Query object details of object 1 live from PRTG server. (not using the value in the sensor tree)
 
    .EXAMPLE
        PS C:\>Receive-PRTGObjectDetail -ObjectId 1 -Server "https://prtg.corp.customer.com" -User "admin" -Pass "1111111"
 
        Query object details of object 1 live from PRTG server. (not using the value in the sensor tree)
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'low'
    )]
    param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjId', 'ID')]
        [int]
        $ObjectId,

        # Name of the object's property to get
        [ValidateNotNullOrEmpty()]
        [string]
        $PropertyName,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {
        $body = @{
            id       = 0
            username = $User
            passhash = $Pass
        }
    }

    Process {
        foreach ($ID in $ObjectID) {
            $body.id = $ID
            Write-Log -LogText "Get details for object ID $ID. ($Server)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            try {
                $Object = (Invoke-RestMethod -Uri "$Server/api/getsensordetails.xml" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop).sensordata
            } catch {
                Write-Log -LogText "Failed to get details for object from prtg. ($Server) Message:$($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
            }

            $ObjectProperty = $Object | Get-Member -MemberType Property | Select-Object -ExpandProperty Name
            $hash = @{}
            if ($PropertyName) {
                #Parameterset: Name
                foreach ($item in $PropertyName) {
                    $PropertiesToQuery = $ObjectProperty | Where-Object { $_ -like $item }
                    foreach ($PropertyItem in $PropertiesToQuery) {
                        if ($hash.$PropertyItem) {
                            Write-Log -LogText "Property $PropertyItem already existis! Skipping this one." -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                        } else {
                            Write-Log -LogText "Get property $PropertyItem from object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                            $hash.Add($PropertyItem, $object.$PropertyItem)
                        }
                    }
                }
                $result = New-Object -TypeName PSCustomObject -Property $hash
            } else {
                #Parameterset: ReturnAll
                Write-Log -LogText "Get all properties from object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                foreach ($PropertyItem in $ObjectProperty) {
                    $hash.Add($PropertyItem, $object.$PropertyItem.'#cdata-section')
                }
            }
            $result = New-Object -TypeName PSCustomObject -Property $hash

            Write-Log -LogText "Found $($result.count) $(if($result.count -eq 1){"property"}else{"properties"}) in object ID $ID" -LogType Info -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            $result
        }
    }
}


function Receive-PRTGObjectProperty {
    <#
    .Synopsis
       Receive-PRTGObject
 
    .DESCRIPTION
       Query an object property directly from PRTGserver and returns.
       Difference to Get-PRTGObjectProperty is, that "Get-PRTGObjectProperty" is working on a modfified sensortree variable in the memory and not on livedata from PRTGServer
 
    .NOTES
       Author: Andreas Bellstedt
 
       adopted from PSGallery Module "PSPRTG"
       Author: Sam-Martin
       Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        PS C:\>Receive-PRTGObject -ObjectId 1 -PropertyName "staus"
        PS C:\>Receive-PRTGObject -ID 1 -Name "staus"
 
        Query property of object 1 live from PRTG server. (not using the value in the sensor tree)
 
    .EXAMPLE
        PS C:\>Receive-PRTGObject -ObjectId 1 -PropertyName "staus" -Server "https://prtg.corp.customer.com" -User "admin" -Pass "1111111"
 
        Query property of object 1 live from PRTG server. (not using the value in the sensor tree)
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'low'
    )]
    param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjId', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the object's property to get
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('Name')]
        [string]
        $PropertyName,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {
        $body = @{
            id       = 0
            name     = $PropertyName
            username = $User
            passhash = $Pass
        }
    }

    Process {
        foreach ($ID in $ObjectID) {
            $body.id = $ID
            # Try to get objectproperty from PRTG
            Write-Log -LogText "Get objectproperty for object ID $ID. ($Server)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            try {
                $Result = (Invoke-RestMethod -UseBasicParsing -Uri "$Server/api/getobjectstatus.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop).prtg.result
                return $result
            } catch {
                Write-Log -LogText "Failed to get objectproperty from prtg. ($Server) Message:$($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
            }
        }
    }

    End {
    }
}


Function Receive-PRTGObjectStatus {
    <#
    .Synopsis
        Receive-PRTGObjectStatus
 
    .DESCRIPTION
        Query the status of an object directly from PRTGserver and returns.
        Difference to Get-PRTGObject is, that "Get-PRTGObject" is working on a modfified sensortree variable in the memory and not on livedata from PRTGServer
 
    .NOTES
        Author: Andreas Bellstedt
 
        adopted from PSGallery Module "PSPRTG"
        Author: Sam-Martin
        Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        PS C:\>Receive-PRTGObjectStatus -ObjectId 1
        PS C:\>Receive-PRTGObjectStatus -ID 1
 
        Query current status of object 1 live from PRTG server. (not using the value in the sensor tree)
 
    .EXAMPLE
        PS C:\>Receive-PRTGObjectStatus -ObjectId 1 -Server "https://prtg.corp.customer.com" -User "admin" -Pass "1111111"
 
        Query current status of object 1 live from PRTG server. (not using the value in the sensor tree)
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'low'
    )]
    [OutputType([PSCustomObject])]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('ID')]
        [ValidateScript( { $_ -gt 0 })]
        [int[]]
        $ObjectID,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {
        $StatusMapping = @{
            1  = "Unknown"
            2  = "Scanning"
            3  = "Up"
            4  = "Warning"
            5  = "Down"
            6  = "No Probe"
            7  = "Paused by User"
            8  = "Paused by Dependency"
            9  = "Paused by Schedule"
            10 = "Unusual"
            11 = "Not Licensed"
            12 = "Paused Until"
        }
    }

    Process {
        foreach ($ID in $ObjectId) {
            try {
                $statusID = (Receive-PRTGObjectProperty -ObjectId $ID -PropertyName 'status' -Server $Server -User $User -Pass $Pass -ErrorAction Stop -Verbose:$false)
            } catch {
                Write-Log -LogText "Unable to get object status from prtg. ($Server) Message:$($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                return
            }

            $hash = @{
                'objid'      = $ID
                "status"     = $StatusMapping[[int]$statusID]
                "status_raw" = $statusID
            }

            $result = New-Object -TypeName PSCustomObject -Property $hash
            $result
        }
    }

    end {}
}


function Remove-PRTGObject {
    <#
    .Synopsis
       Remove-PRTGObject
 
    .DESCRIPTION
       Remove an object from PRTGserver and returns.
       Difference to Get-PRTGObject is, that "Get-PRTGObject" is working on a modfified sensortree variable in the memory and not on livedata from PRTGServer
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
       adopted from PSGallery Module "PSPRTG"
       Author: Sam-Martin
       Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       PS C:\>Remove-PRTGObject -ObjectId 1
 
       Remove object with ID 1
 
    .EXAMPLE
       PS C:\>Get-PRTGObject -ObjectId 1 | Remove-PRTGObject
 
       Remove object with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'high'
    )]
    [OutputType([Boolean])]
    Param(
        # ID of the object to delete
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('objID', 'ID')]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [int[]]
        $ObjectID,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree,

        # returns the deleted object
        [Switch]
        $PassThru
    )

    Begin {}

    Process {
        $Deleted = @()

        foreach ($ID in $ObjectID) {
            $body = @{
                id       = $ID
                approve  = 1
                username = $User
                passhash = $Pass
            }

            # Check for object on sensor tree
            try {
                $Object = Get-PRTGObject -ObjectID $id -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
            } catch {
                Write-Log -LogText "Cannot find object ID $ID" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
                break
            }

            if ($pscmdlet.ShouldProcess("$($Object.name) objID $ID", "Remove PRTG object")) {
                #Remove in PRTG
                try {
                    Write-Log -LogText "Remove object ID $ID ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $Result = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/deleteobject.htm " -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to delete object $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
                    break
                }

                #Remove on SensorTree
                if ($Result.StatusCode -eq 200) {
                    $ToDelete = $SensorTree.SelectSingleNode("//*[id='$ID']")
                    while ($ToDelete -ne $null) {
                        $Deleted = $ToDelete.ParentNode.RemoveChild($ToDelete)
                        $ToDelete = $SensorTree.SelectSingleNode("//*[id='$ID']")
                    }
                    Write-Log -LogText "Remove object ID $($ID) with name ""$($Deleted.name)"". ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                } else {
                    Write-Log -LogText "Failed to delete object ID $($Deleted.ObjID). ($($Server)) Message:$($Result.StatusCode) $($Result.StatusDescription)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                    break
                }

                #Write-Uutput
                if ($PassThru) { Set-TypesNamesToPRTGObject -PRTGObject $Deleted }
            }
        }
    }

    End {}
}

function Remove-PRTGObjectTAG {
    <#
    .Synopsis
       Remove-PRTGObjectTAG
 
    .DESCRIPTION
       Remove a text from the tags property of an PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       PS C:\>Remove-PRTGObjectTAG -ObjectId 1 -TAGName "MyTAG"
 
       Remove "MyTAG" from an object with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int]
        $ObjectId,

        # Name of the object's property to set
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $false)]
        [ValidateNotNullOrEmpty()]
        [string[]]
        $TAGName,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree,

        # Skip errors if an tag is not present
        [Switch]
        $Force,

        # Output the deleted object
        [Switch]
        $PassThru
    )

    Begin {}

    Process {
        foreach ($ID in $ObjectId) {
            $break = $false

            #Get the object
            Write-Log -LogText "Gather object tags from object ID $ID." -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            try {
                $Object = Get-PRTGObject -ID $ID -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
            } catch {
                Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                return
            }

            #Build and check TAG lists
            $TAGListExisting = $Object.tags.Split(' ')
            $TAGListToSet = $Object.tags
            $TAGListCount = 0

            foreach ($TAG in $TAGName) {
                if ($TAG -in $TAGListExisting) {
                    $TAGListToSet = $TAGListToSet -replace [regex]::Escape($TAG), ''
                    $TAGListCount++
                } else {
                    if ($Force) {
                        Write-Log -LogText "Skipping tag ""$($TAG)"", because it is not present on object id $ID" -LogType Warning -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Warning
                    } else {
                        Write-Log -LogText "Tag ""$($TAG)"" is not present on object id $ID" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                        $break = $true
                        break
                    }
                }
            }

            $TAGListToSet = $TAGListToSet.Trim()
            if ($break) { break }

            #set TAG list to PRTG object
            $MessageText = "Remove $($TAGListCount) $(if($TAGListCount -eq 1) {"tag"} else {"tags"})"
            if ($pscmdlet.ShouldProcess("objID $ID", $MessageText)) {
                Write-Log -LogText $MessageText -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                try {
                    #Set in PRTG
                    Set-PRTGObjectProperty -ObjectId $ID -PropertyName tags -PropertyValue $TAGListToSet -Server $Server -User $User -Pass $Pass -ErrorAction Stop -Verbose:$false

                    #Set on object to return
                    $Object.tags = $TAGListToSet

                    #Set in SensorTree variable
                    $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/tags").InnerText = $TAGListToSet
                } catch {
                    Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                    if (-not $Force) { break }
                }
            }

            #output the object
            if ($PassThru) { $Object }

            #clear up the variable mess
            Remove-Variable TAG, TAGListExisting, TAGListToSet, Object, MessageText -Force -ErrorAction Ignore -Confirm:$false -Verbose:$false -Debug:$false -WhatIf:$false
        }
    }

    End {}
}


function Rename-PRTGObject {
    <#
    .Synopsis
        Rename-PRTGObject
 
    .DESCRIPTION
        Rename an PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
        Author: Andreas Bellstedt
 
        adopted from PSGallery Module "PSPRTG"
        Author: Sam-Martin
        Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Rename-PRTGObject -ObjectId 1 -NewName "NewName"
 
        Rename object with ID 1 to "NewName"
 
    .EXAMPLE
        Get-PRTGObject "MyDevice" | Rename-PRTGObject -NewName "MyNewDevice"
 
        Rename "MyDevice" to "MyNewDevice"
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [int]
        $ObjectId,

        # New name of the object
        [string]
        $NewName,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # SensorTree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    $body = @{
        id       = $ObjectId
        value    = $NewName
        username = $User
        passhash = $Pass
    }

    Write-Log -LogText "Get object details from object ID $ObjectId. ($Server)" -LogType Query -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
    try {
        $object = Get-PRTGObject -ID $ObjectId -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
    } catch {
        Write-Log -LogText $_.exception.message -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
        return
    }

    if ($pscmdlet.ShouldProcess("objID $ObjectId", "Rename PRTG object to '$NewName'")) {
        # Set in PRTG
        try {
            Write-Log -LogText "Set new name ""$($NewName)"" on object ID $ObjectId. ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
            $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/rename.htm" -Method Get -Body $body -Verbose:$false
        } catch {
            Write-Log -LogText "Failed to rename object ID $ObjectId. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
            return
        }

        # Set on SensorTree variable
        $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ObjectId)]/name").InnerText = $object.Name

        # Write output
        Get-PRTGObject -ID $ObjectId -SensorTree $SensorTree -Verbose:$false -ErrorAction Stop
    }
}


function Set-PRTGObjectAlarmAcknowledgement {
    <#
    .Synopsis
       Set-PRTGObjectAlarmAcknowledgement
 
    .DESCRIPTION
       Acknowledge an alarm on a PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Set-PRTGObjectAlarmAcknowledgement -ObjectId 1
 
       Set alarm on object 1
 
    .EXAMPLE
       Set-PRTGObjectAlarmAcknowledgement -ObjectId 1 -Message "Done by User01"
 
       Set alarm on object 1 with indidual message
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    [Alias('Set-PRTGObjectAlamAcknowledgement')] # in because of typo in cmdletname in previous version, not to produce a breaking change with the typo fix
    Param(
        # ID of the object to resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Message to associate with the pause event]
        [string]
        $Message,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {}

    Process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                username = $User
                passhash = $Pass
            }
            if ($Message) { $body.Add("ackmsg", $Message) }

            if ($pscmdlet.ShouldProcess("objID $Id", "Acknowledge alarm on object")) {
                try {
                    Write-Log -LogText "Acknowledge alarm on object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/acknowledgealarm.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to acknowledge alarm on object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                }
            }
        }
    }

    End {}
}


function Set-PRTGObjectPriority {
    <#
    .Synopsis
        Set-PRTGObjectPriority
 
    .DESCRIPTION
        Set priority value on a PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
        Author: Andreas Bellstedt
 
    .LINK
        https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
        Set-PRTGObjectPriority -ObjectId 1 -Priority 3
 
        Set priority on object 1
 
    .EXAMPLE
        Set-PRTGObjectPriority -ObjectId 1 -Priority 3 -PassThru
 
        Set priority on object 1 and output the object
 
    .EXAMPLE
        Set-PRTGObjectPriority -ObjectId 1 -Priority 3 -Server "https://prtg.corp.customer.com" -User "admin -Pass "1111111"
 
        Set priority on object 1 on explicitly specified server with custom credentials.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Priority value to set
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern("[1-5]")]
        [int]
        $Priority,

        # returns the changed object
        [Switch]
        $PassThru,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    Begin {}

    Process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                prio     = $Priority
                username = $User
                passhash = $Pass
            }

            if ($pscmdlet.ShouldProcess("objID $Id", "Set priority $Priority to object")) {
                #Set in PRTG
                try {
                    Write-Log -LogText "Set priority $Priority to object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/setpriority.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to set priortiy object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                }

                #Set on SensorTree Variable
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($ID)]/priority").InnerText = $Priority

                #Write-Output
                if ($PassThru) { Get-PRTGObject -ObjectID $id -SensorTree $SensorTree -Verbose:$false }
            }
        }
    }

    End {}
}

function Set-PRTGObjectProperty {
    <#
    .Synopsis
       Set-PRTGObjectProperty
 
    .DESCRIPTION
       Set the property of an PRTG object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
       adopted from PSGallery Module "PSPRTG"
       Author: Sam-Martin
       Github: https://github.com/Sam-Martin/prtg-powershell
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Set-PRTGObjectProperty -ObjectId 1 -PropertyName "Name" -PropertyValue "NewValue"
 
       Set object property in PRTG
 
    .EXAMPLE
       Set-PRTGObjectProperty -ObjectId 1 -PropertyName "Name" -PropertyValue "NewValue" -Server "https://prtg.corp.customer.com" -User "admin -Pass "1111111"
 
       Set object property in PRTG
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to pause/resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -ge 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the object's property to set
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $PropertyName,

        # Value to which to set the property of the object
        [string]
        $PropertyValue,

        # returns the changed object
        [Switch]
        $PassThru,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    Begin {}

    Process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                action   = 1
                name     = $PropertyName
                value    = $PropertyValue
                username = $User
                passhash = $Pass
            }

            if ($pscmdlet.ShouldProcess("objID $Id", "Set property '$PropertyName' to '$PropertyValue' on PRTG object")) {
                # Set property in PRTG
                try {
                    Write-Log -LogText "Set property ""$PropertyName"" to ""$PropertyValue"" on object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/setobjectproperty.htm" -Method Get -Body $body -Verbose:$false
                } catch {
                    Write-Log -LogText "Failed to set value $PropertyValue on property $PropertyName. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -Error -NoFileStatus
                }

                # Set property in SensorTree Variable
                if ($PropertyName -eq "id") {
                    $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($id)]").SetAttribute($PropertyName, $PropertyValue)
                }
                $SensorTree.SelectSingleNode("/prtg/sensortree/nodes/group//*[id=$($id)]/$PropertyName").InnerText = $PropertyValue

                # Write-Output
                if ($PassThru) { Get-PRTGObject -ObjectID $id -SensorTree $SensorTree -Verbose:$false }
            }
        }
    }

    End {}
}


function Test-PRTGObjectNotification {
    <#
    .Synopsis
       Test-PRTGObjectNotification
 
    .DESCRIPTION
       Test a notifcation action for a object
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Test-PRTGObjectNotification -ObjectId 1
 
       Test a notifcation action for object with ID 1
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Default',
        SupportsShouldProcess = $true,
        ConfirmImpact = 'medium'
    )]
    Param(
        # ID of the object to resume
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 } )]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Url for PRTG Server
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { if ($_.StartsWith("http")) { $true } else { $false } } )]
        [String]
        $Server = $script:PRTGServer,

        # User for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $User = $script:PRTGUser,

        # Password or PassHash for PRTG Authentication
        [ValidateNotNullOrEmpty()]
        [String]
        $Pass = $script:PRTGPass
    )

    Begin {}

    Process {
        foreach ($id in $ObjectId) {
            $body = @{
                id       = $id
                username = $User
                passhash = $Pass
            }

            if ($pscmdlet.ShouldProcess("objID $Id", "Test notification for object")) {
                try {
                    Write-Log -LogText "Test notification for object ID $id ($Server)" -LogType Set -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -DebugOutput
                    $null = Invoke-WebRequest -UseBasicParsing -Uri "$Server/api/notificationtest.htm" -Method Get -Body $Body -Verbose:$false -Debug:$false -ErrorAction Stop
                } catch {
                    Write-Log -LogText "Failed to test notification for object ID $id. $($_.exception.message)" -LogType Error -LogScope $MyInvocation.MyCommand.Name -NoFileStatus -Error
                }
            }
        }
    }

    End {}
}

function Get-PRTGProbe {
    <#
    .Synopsis
       Get-PRTGProbe
 
    .DESCRIPTION
       Returns one or more probes from sensortree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGProbe
       Query all probes from the default sensortree (global variable after connect to PRTG server)
 
       Get-PRTGProbe -SensorTree $SensorTree
       Query probes by name from a non default sensortree
 
    .EXAMPLE
       Get-PRTGProbe -Name "Probe01"
       Query probes by name
 
       Get-PRTGProbe -Name "Probe01", "Probe*"
       # Multiple names are possible
 
       "Probe01" | Get-PRTGProbe
       # Piping is also possible
 
    .EXAMPLE
       Get-PRTGProbe -ObjectId 1
       Query probes by object ID
 
       Get-PRTGProbe -ObjID 1, 100
       Get-PRTGProbe -ID 1, 100 -SensorTree $SensorTree
       # Multiple IDs are possible
 
       1 | Get-PRTGProbe
       # Piping is also possible
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the PRTG object
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ID', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {$_ -gt 0})]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the Probe
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Name', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Name,

        # sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {
        $queryParam = @{
            "Type" = "probenode"
            "SensorTree" = $SensorTree
            "Verbose" = $false
        }
    }

    process {
        $result = @()

        switch ($PsCmdlet.ParameterSetName) {
            'ID' {
                foreach ($item in $ObjectId) {
                    $result += Get-PRTGObject -ObjectID $item @queryParam
                }
            }

            'Name' {
                foreach ($item in $Name) {
                    $result += Get-PRTGObject -Name $item @queryParam
                }
            }

            Default {
                $result = Get-PRTGObject @queryParam
            }
        }

        $result
    }

    end {}
}

function Get-PRTGSensor {
    <#
    .Synopsis
       Get-PRTGSensor
 
    .DESCRIPTION
       Returns one or more sensors from sensortree
 
    .NOTES
       Author: Andreas Bellstedt
 
    .LINK
       https://github.com/AndiBellstedt/PoShPRTG
 
    .EXAMPLE
       Get-PRTGSensor
       Query all sensors from the default sensortree (global variable after connect to PRTG server)
 
    .EXAMPLE
       Get-PRTGSensor -SensorTree $SensorTree
       Query sensors by name from a non default sensortree
 
    .EXAMPLE
       Get-PRTGSensor -Name "Sensor01"
 
       Query sensors by name
 
    .EXAMPLE
       Get-PRTGSensor -Name "Sensor01", "Sensor*"
 
       Multiple names are possible
 
    .EXAMPLE
       "Sensor01" | Get-PRTGSensor
 
       Piping is also possible
 
    .EXAMPLE
       Get-PRTGSensor -ObjectId 1
 
       Query sensors by object ID
 
    .EXAMPLE
       1 | Get-PRTGSensor
 
       Piping is also possible
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'ReturnAll',
        SupportsShouldProcess = $false,
        ConfirmImpact = 'Low'
    )]
    Param(
        # ID of the PRTG object
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'ID', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( { $_ -gt 0 })]
        [Alias('ObjID', 'ID')]
        [int[]]
        $ObjectId,

        # Name of the sensor
        [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Name', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]
        $Name,

        # Sensortree from PRTG Server
        [ValidateNotNullOrEmpty()]
        [xml]
        $SensorTree = $script:PRTGSensorTree
    )

    begin {
        $queryParam = @{
            "Type" = "sensor"
            "SensorTree" = $SensorTree
            "Verbose" = $false
        }
    }

    process {
        $result = @()

        switch ($PsCmdlet.ParameterSetName) {
            'ID' {
                foreach ($item in $ObjectId) {
                    $result += Get-PRTGObject -ObjectID $item @queryParam
                }
            }

            'Name' {
                foreach ($item in $Name) {
                    $result += Get-PRTGObject -Name $item @queryParam
                }
            }

            Default {
                $result = Get-PRTGObject @queryParam
            }
        }

        $result
    }

    end {}
}


<#
This is an example configuration file
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


<#
# Example Configuration
Set-PSFConfig -Module 'PoShPRTG' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
#>


Set-PSFConfig -Module 'PoShPRTG' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'PoShPRTG' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."

<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'NPSLogFile.ScriptBlockName' -Scriptblock {
 
}
#>


<#
# Example:
Register-PSFTeppScriptblock -Name "PoShPRTG.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>


<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name PoShPRTG.alcohol
#>


New-PSFLicense -Product 'PoShPRTG' -Manufacturer 'Andreas Bellstedt' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2019-01-05") -Text @"
Copyright (c) 2019 Andreas Bellstedt
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@

#endregion Load compiled code