Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #requires -version 4.0
- # ___ ___ _____ ___
- #| _ \ _ \_ _/ __|
- #| _/ / | || (_ |
- #|_| |_|_\ |_| \___|
- # SCVMM & HyperV-VM Sensors
- # ================================
- # This sensor will monitor HyperV hosts and their virtual machines using SCVMM.
- # It also supports metascan (https://kb.paessler.com/en/topic/68109), so you don't have
- # to do all the configuration work. Note that this will only work in an upcoming stable release
- #
- # Version History
- # ----------------------------
- # 1.4 [Added] Support for Remote Probes
- # 1.3 [Changed] Switched to HTTP Push Sensor based system. No device templates needed, better for large environments.
- # [Notes ] It's an simple EXE/Script sensor now, no longer EXE/Script Advanced.
- # Device templates are no longer needed, as the sensor automatically creates new sensors for new hosts and VMs with each run
- # With every run, we only run Get-VMHost and Get-VM once on the SCVMM host, receiving all objects at once, not once for every VM
- # A GUID file is maintained for every SCVMM host that will prevent sensor duplicates
- # Performance wise, it should be way easier and faster. Three VMs and one host take 2s to scan.
- # Better, more verbose script output to see what the script actually does, receives and evaluates.
- # 1.2 [Changed] Updated device template, changed naming template of VM sensors
- # 1.13 [Fixed] Paramter was wrongly passed to Get-VMHost and Get-VM
- # 1.12 [Fixed] Values weren't retrieved correctly when multiple VMs were present.
- # 1.1 [Fixed] Values weren't retrieved correctly due to lookup bug
- # 1.0 Initial Release
- #
- # Credits
- # ----------------------------
- # RICHARD GARNER, computerservicecentre.com
- # For going through countless tests and bugfixing with me. And improvement ideas - Thanks! :)
- # # # # # # # # # # # # # # # # # # # # # # # # # #
- <#
- .SYNOPSIS
- This sensor will monitor HyperV hosts and their virtual machines using SCVMM.
- .DESCRIPTION
- Installation:
- 1. Save the script as PRTG-SCVMM-HyperV.ps1 under <PRTG Application folder>\Custom Sensors\EXE\
- 2. Open up your SCVMM device in PRTG
- 3. Add a new HTTP Push Data (Advanced) sensor to the device, name it any way you like (e.g. "Template Sensor"), and pause it. Note it's sensor ID
- 4. Create a new EXE/Script sensor and select the PRTG-SCVMM-HyperV.ps1 script
- 5. Set the timeout to 900 seconds
- 6. Enter the following parameters:
- -prtgHostName <your prtg url, e.g. http://prtg.acme.com or https://prtg.acme.com>
- -prtgPushPort <the push port you're using in the template sensor
- -prtgProbeAddress <the address of the remote probe the sensor resides on (if so)>
- -prtgUserName <a user with read/write permissions to the device, or a PRTG administrator>
- -prtgPasshash <the passhash of the above user>
- -prtgTemplateSensor <the ID (not token) of the HTTP Push sensor>
- -prtgScvmmDeviceID %deviceid
- -ComputerName %host
- -Userdomain %windowsdomain
- -Username %windowsuser
- -Password %windowspassword
- When you enter the data in the script directly, you can also test it on the fly. Initially, it will look like the attached screenshot #1, consequential runs will look like #2.
- Note that the sensors may not have data directly after the first run. This is due to the startup of the sensors taking some time and the script may push values prior to that.
- .PARAMETER prtgHostName
- The URL of your PRTG webinterface, e.g. http://prtg.acme.com or https://prtg.acme.com
- .PARAMETER prtgPort
- The port used by your PRTG webinterface, e.g. 80 or 443
- .PARAMETER prtgPushPort
- The push port you configured in the template HTTP Push Advanced sensor
- .PARAMETER prtgProbeAddress
- If your SCVMM is monitored by a remote probe, enter it's address here, e.g. http://prtgprobe.acme.com
- .PARAMETER prtgUserName
- A user with read/write permissions to the device, or a PRTG administrator
- .PARAMETER prtgPassHash
- The passhash of the above user
- .PARAMETER prtgScvmmDeviceId
- This is the ID of the device that resembles your SCVMM server - use %deviceid
- .PARAMETER prtgTemplateSensor
- This is the ID of the HTTP Push Data (Advanced) template sensor (not the sensor token, but the PRTG ID)
- .PARAMETER ComputerName
- Your SCVMM host - use %host here.
- .PARAMETER Userdomain
- The domain of the user that will be used for remote powershell access - use %windowsdomain
- .PARAMETER Username
- The username of the user that will be used for remote powershell access - use %windowsuser
- .PARAMETER Password
- The password of the user that will be used for remote powershell access - use %windowspassword
- .PARAMETER verbose
- When this is set, the script will output debug messages. This can always be enabled as it doesn't bother PRTG.
- .EXAMPLE
- C:\PS> .\Get-Events.ps1 -ComputerName %host -Username "%windowsdomain\%windowsuser" -Password "%windowspassword" -ProviderName "Microsoft-Windows-Immersive-Shell" -Channel "Microsoft-Windows-TWinUI/Operational" -LimitEntries 1 -MaxAge 1 -EventID 1719 -Level 4
- #>
- param(
- [string]$prtgHostName = "",
- [string]$prtgProbeAddress = "",
- [string]$prtgPort = 80,
- [int]$prtgPushPort = 5050,
- [string]$prtgUserName = '',
- [string]$prtgPasshash = 0,
- [int]$prtgScvmmDeviceId = 0,
- [int]$prtgTemplateSensor = 0,
- [string]$computername = "",
- [string]$userdomain = "",
- [string]$username = "",
- [string]$password = "",
- [switch]$verbose = $true
- )
- $global:counter = 0;
- $scriptPath = "C:\Program Files (x86)\PRTG Network Monitor\Custom Sensors\EXE";
- $guidFile = [string]::Format("{0}\{1}-guidfile.dat",$scriptPath,$computername);
- $global:webclient = New-Object System.Net.WebClient;
- # this will output debug messages to the console
- function Console-ShowMessage([string]$type,$message){
- if($verbose){
- Write-Host ("[{0}] " -f (Get-Date)) -NoNewline;
- switch ($type){
- "success" { Write-Host " success " -BackgroundColor Green -ForegroundColor White -NoNewline; }
- "information" { Write-Host " information " -BackgroundColor DarkCyan -ForegroundColor White -NoNewline; }
- "warning" { Write-Host " warning " -BackgroundColor DarkYellow -ForegroundColor White -NoNewline; }
- "error" { Write-Host " error " -BackgroundColor DarkRed -ForegroundColor White -NoNewline; }
- default { Write-Host " notes " -BackgroundColor DarkGray -ForegroundColor White -NoNewline; }
- }
- Write-Host (" {0}{1}" -f $message,$Global:blank)
- }
- }
- # if the guid files don't exist, create them
- if(!(Test-Path $guidFile)) { New-Item -ItemType File -Path $guidFile | Out-Null; Console-ShowMessage -type "information" -message "created GUID file: $($guidFile)"; }
- Console-ShowMessage "information" "GUID file path: $($guidFile)";
- $Stopwatch = [system.diagnostics.stopwatch]::new()
- #region xml item
- $channel = @"
- <result>
- <channel>{0}</channel>
- <value>{1}</value>{2}
- <showChart>{3}</showChart>
- {4}
- {5}
- {6}
- </result>
- "@
- #endregion
- #region Function Library
- # this will return a value only if it's an integer
- function This-Numeric ($Value) {
- return $Value -match "^[\d\.]+$"
- }
- # Since we need lookup files to show this properly, we'll have to convert
- # the states received to integers
- function This-StateConvert([string]$Type, [string]$CurrentStatus){
- # replacement tables
- [hashtable]$States = @{
- "OverallStatesUnknown" = -1
- "OverallStatesAdding " = 1
- "OverallStatesNotResponding" = 2
- "OverallStatesReassociating" = 3
- "OverallStatesRemoving" = 4
- "OverallStatesUpdating" = 5
- "OverallStatesPending" = 6
- "OverallStatesMaintenanceMode" = 7
- "OverallStatesNeedsAttention" = 8
- "OverallStateOk" = 9
- "OverallStatesLimited" = 10
- "CommunicationStateUnknown" = -1
- "CommunicationStateResponding" = 0
- "CommunicationStateNotResponding" = 1
- "CommunicationStateAccessDenied " = 2
- "CommunicationStateConnecting" = 3
- "CommunicationStateDisconnecting" = 4
- "CommunicationStateResetting" = 5
- "CommunicationStateNoConnection" = 6
- "ComputerStateUnknown" = -1
- "ComputerStateAdding" = 0
- "ComputerStateRemoving" = 1
- "ComputerStateResponding" = 2
- "ComputerStateNotResponding" = 3
- "ComputerStateAccessDenied" = 4
- "ComputerStateUpdating" = 5
- "ComputerStateReassociating" = 6
- "ComputerStatePending" = 7
- "ComputerStateMaintenanceMode" = 8
- "ClusterNodeStatusUnknown" = 0
- "ClusterNodeStatusRunning" = 1
- "ClusterNodeStatusStopped" = 2
- "ClusterNodeStatusNoCluster" = 3
- "VirtualServerStateUnknown" = 0
- "VirtualServerStateRunning" = 1
- "VirtualServerStateStopped" = 2
- "VMStateUnknown" = -1
- "VMStateRunning" = 0
- "VMStatePowerOff" = 1
- "VMStatePoweringOff" = 2
- "VMStateSaved" = 3
- "VMStateSaving" = 4
- "VMStateRestoring" = 5
- "VMStatePaused" = 6
- "VMStateDiscardSavedState" = 10
- "VMStateStarting" = 11
- "VMStateMergingDrives" = 12
- "VMStateDeleting" = 13
- "VMStateDiscardingDrives" = 80
- "VMStatePausing" = 81
- "VMStateUnderCreation" = 100
- "VMStateCreationFailed" = 101
- "VMStateStored" = 102
- "VMStateUnderTemplateCreation" = 103
- "VMStateTemplateCreationFailed" = 104
- "VMStateCustomizationFailed" = 105
- "VMStateUnderUpdate" = 106
- "VMStateUpdateFailed" = 107
- "VMStateUnderMigration" = 200
- "VMStateMigrationFailed" = 201
- "VMStateCreatingCheckpoint" = 210
- "VMStateDeletingCheckpoint" = 211
- "VMStateRecoveringCheckpoint" = 212
- "VMStateCheckpointFailed" = 213
- "VMStateInitializingCheckpointOperation" = 214
- "VMStateFinishingCheckpointOperation" = 215
- "VMStateMissing" = 220
- "VMStateHostNotResponding" = 221
- "VMStateUnsupported" = 222
- "VMStateIncompleteVMConfig" = 223
- "VMStateUnsupportedSharedFiles" = 224
- "VMStateUnsupportedCluster" = 225
- "VMStateP2VCreationFailed" = 240
- "VMStateV2VCreationFailed" = 250
- }
- return $states[$Type+$CurrentStatus]
- }
- # This will turn the metrics received from the SCVMM into PRTG channels,
- # with their corresponding channel settings.
- function This-PrtgXmlWrapper($Metrics){
- $cleanMetrics = $Metrics;
- foreach ($metric in $cleanMetrics.GetEnumerator() | sort -Property Name)
- {
- $Lookup = "";
- $Volume = "";
- #region Metric Properties Replacement
- # get the unit of the item
- If($metric.Value[1] -ne 0)
- { $Unit = [string]::Format("<unit>{0}</unit>", $metric.Value[1])}
- else
- { $Unit = "<unit>Count</unit>" }
- # get the value lookup of the item
- If($metric.Value[2] -ne 0)
- { $Lookup = [string]::Format("<ValueLookup>{0}</ValueLookup>",$metric.Value[2]); }
- # should the metric show up in the graph?
- If($metric.Value[3] -eq 0)
- { $showChart = 0 }
- else
- { $showChart = 1; }
- # can the item fire a change trigger?
- if($metric.Value[4] -ne 0)
- { $notifyChanged = "<NotifyChanged></NotifyChanged>" }
- else
- { $notifyChanged = "" }
- if($metric.value[5])
- { $Volume = "<VolumeSize>$($metric.value[5])</VolumeSize>" }
- #endregion
- # replace the state strings with their corresponding IDs
- if(!(This-Numeric $metric.Value[0]))
- { $metric.Value[0] = (This-StateConvert -Type $metric.Name -CurrentStatus $metric.Value[0]) }
- $channels += [string]::Format($channel,$metric.Name, $metric.Value[0], $Unit,$showChart,$notifyChanged,$Lookup,$Volume)
- }
- return $channels;
- }
- # If there's an error, only this will be outputted
- function This-PrtgError($Message){
- if(!($verbose)){
- Write-Host "<?xml version='1.0' encoding='UTF-8' ?><prtg>"
- Write-Host ([string]::Format("<error>1</error><text>{0}</text>",$Message));
- Write-Host "</prtg>";
- exit 0;
- }
- }
- # We need to ignore $null values, this will cleanse the metrics accordingly
- function This-ResultSanitize($Metrics){
- $CleanMetric = @{};
- foreach ($metric in $Metrics.GetEnumerator() | sort -Property Name)
- { if($metric.Value[0] -ne $null) {$CleanMetric.Add($metric.key,$metric.value)} }
- return $CleanMetric;
- }
- # @Output: PSCredentialObject
- #
- # This function will generate a PowerShell Credential object for the
- # given username, password and domainname
- function This-GenerateCredential(){
- try{
- # Generate credentials object for authentication
- $SecPasswd = ConvertTo-SecureString $password -AsPlainText -Force
- $Credentials = New-Object System.Management.Automation.PSCredential ([string]::Format("{0}\{1}",$userdomain,$username), $secpasswd)
- return $Credentials;
- }
- catch{
- catch{ This-PrtgError -Message "Couldn't connect to the server or execute the given commands. Please check if WinRM is enabled and allowed." }
- }
- }
- # @Output: Update HTTP Push (Advanced) sensors
- function This-UpdateHTTPPushSensors {
- param([string]$type, [pscustomobject[]]$objects)
- $counter = 0;
- # first load the GUID list to check the GUIDs against
- $guidList = ( Get-Content $guidFile );
- # add sensors that are not in our list as a HTTP Push Content sensor
- foreach($object in $objects){
- if(!($guidList -match $object.id))
- {
- if(This-AddHTTPPushSensors -sensorName $object.name -Token $object.id)
- {
- Console-ShowMessage -type "success" -message "Sensor for $($object.name) created successfully.";
- # append the GUID to the file so we don't add duplicates in future scans
- $object | Select "ID" | ft -HideTableHeaders | Out-File -FilePath $guidFile -Append
- $counter++;
- $global:counter++;
- }
- else
- {
- Console-ShowMessage -type "error" -message "Could not create sensor for $($object.name).";
- }
- }
- else
- { Console-ShowMessage -type "information" -message "$($object.name) with ID $($object.id) is already existing as a sensor."; }
- }
- Console-ShowMessage -type "information" -message "$($counter) new $($type) have been added to PRTG.";
- }
- # @Output: Create HTTP Push (Advanced) sensors
- #
- # This function will generate the PRTG Metascan Items for the VMs in XML format
- function This-AddHTTPPushSensors {
- param([string]$sensorName, [guid]$token)
- try{
- Console-ShowMessage "information" "Starting sensor creation for $($sensorName) (GUID: $($token))";
- # create the URL for duplicating the original sensor
- $url = [string]::Format("{0}:{1}/api/duplicateobject.htm?id={2}&name={3}&targetid={4}&username={5}&passhash={6}",
- $prtgHostName, $prtgPort, $prtgTemplateSensor, $sensorName, $prtgScvmmDeviceId, $prtgUserName, $prtgPasshash);
- # call the URL and store the content, we need the actual ID of the new sensor to change the settings and unpause it
- $request = [System.Net.WebRequest]::Create($url);
- $request.AllowAutoRedirect = $false
- $response=$request.GetResponse();
- If ($response.StatusCode -eq "Redirect")
- {
- $response.GetResponseHeader("Location") -match '\d{3,}' | Out-Null;
- $newSensorID = $Matches[0];
- Console-ShowMessage -type "success" -message "Sensor created successfully. New sensor ID: $($newSensorID)";
- }
- Else
- { Console-ShowMessage "error" "Sensor creation failed. PRTG returned: $($response.StatusCode)"; }
- # modify the token to resemble the GUID of the device
- $url = [string]::Format("{0}:{1}/api/setobjectproperty.htm?id={2}&name={3}&value={4}&username={5}&passhash={6}",
- $prtgHostName, $prtgPort, $newSensorID, "httppushtoken", $token, $prtgUserName, $prtgPasshash);
- $UpdateSensorResult = (Invoke-WebRequest $url)
- if($UpdateSensorResult.StatusCode -eq "200")
- { Console-ShowMessage -type "success" -message "Changed sensor token to GUID of the host/vm."; }
- # start the sensors
- $url = [string]::Format("{0}:{1}/api/pause.htm?id={2}&action=1&username={3}&passhash={4}",
- $prtgHostName, $prtgPort, $newSensorID, $prtgUserName, $prtgPasshash);
- $StartSensorResult = (Invoke-WebRequest $url)
- if($StartSensorResult.StatusCode -eq "200")
- { Console-ShowMessage -type "success" -message "Sensor has been started succesfully."; }
- return $true
- }
- catch{ return $false; }
- }
- # @Output: PowerShell Object Array with all hosts
- #
- # This function will retrieve all VM hosts available
- function This-GetHostsAndVms() {
- Console-ShowMessage -type "information" -message "Retrieving HyperV hosts and VMs...";
- try{
- $objects = (Invoke-Command -ComputerName $computername -ScriptBlock {
- # load the correct snapins:
- if ((new-object 'version' (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft System Center Virtual Machine Manager Server\Setup" ProductVersion | select -expandproperty productversion)) -ge (new-object 'Version' 3,0)) { Import-Module ((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft System Center Virtual Machine Manager Server\Setup" InstallPath | select -ExpandProperty InstallPath) + "bin\psModules\virtualmachinemanager") } else { Add-PSSnapin Microsoft.SystemCenter.VirtualMachineManager }
- if ((new-object 'version' (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft System Center Virtual Machine Manager Server\Setup" ProductVersion | select -expandproperty productversion)) -ge (new-object 'Version' 6,2)) { Get-SCVMMServer localhost | Out-Null } else { Get-VMMServer localhost | Out-Null }
- $hosts = (Get-VMHost);
- $vms = (Get-VM);
- # The cmdlets above give back a single object or an array of objects.
- # For the sake of simplicity, let's convert them, so they're always arrays
- if($hostList -isnot [system.array])
- { $hostList = @($hosts) }
- if($vmList -isnot [system.array])
- { $vmList = @($vms) }
- $objects = @($hostList,$vmList)
- return $objects;
- } -Credential (This-GenerateCredential)
- )
- return $objects;
- }
- catch{
- Console-ShowMessage -type "error" -message "Couldn't connect to the server or execute the given commands. Please check if WinRM is enabled and allowed.";
- This-PrtgError -Message "Couldn't connect to the server or execute the given commands. Please check if WinRM is enabled and allowed."
- }
- }
- # @Param token: the GUID of the VM/host
- # @Param data: the channels of the VM/host
- function This-PushData([string]$token, [string]$data){
- $pushData = ([string]::Format("<prtg>{0}</prtg>",$data)).replace("`n"," ").Replace(" ",'%20');
- if($prtgProbeAddress.Length -ne 0)
- { $url = [string]::Format("{0}:{1}/{2}?content={3}",($prtgProbeAddress -replace "https","http"),$prtgPushPort,$token,$pushData); }
- else
- { $url = [string]::Format("{0}:{1}/{2}?content={3}",($prtgHostName -replace "https","http"),$prtgPushPort,$token,$pushData); }
- try {
- Invoke-WebRequest $url -Method Get | Out-Null
- Console-ShowMessage -type "success" -message "Data for host with ID $($token) pushed to corresponding sensor.";
- }
- catch {
- Console-ShowMessage -type "error" -message "Couldn't push the data to the configured PRTG server using the specified token ($($token)).";
- This-PrtgError -Message "Couldn't push the data to the configured PRTG server using the specified token ($($token))."
- }
- }
- function This-GetSCVMMObjects(){
- # lets retrieve hosts and vms first
- $Objects = (This-GetHostsAndVms)
- # ... and seperate them
- $VMHosts = $objects[0];
- $VMs = $objects[1];
- Console-ShowMessage -type "success" -message "$($VMHosts.count) HyperV hosts have been found.";
- Console-ShowMessage -type "success" -message "$($VMs.count) Virtual Machines have been found.";
- # let's add the sensor first, before we do anything
- This-UpdateHTTPPushSensors -objects $VMHosts -type "HyperV Hosts"
- This-UpdateHTTPPushSensors -objects $VMs -type "Virtual Machines"
- # we have to wait for the sensors to become active.
- if($global:counter -gt 0){
- Console-ShowMessage -type "information" "Waiting for new push sensors to become active"
- Start-Sleep 10;
- }
- # now that the sensors are there, let's push the values to them
- foreach($VMHost in $VMHosts){
- $metrics = @{
- "OverallState" = @($VMHost.OverallState,"Custom","prtg.standardlookups.hyperv.hoststatus",1,"<NotifyChanged>");
- "CommunicationState" = @($VMHost.CommunicationState,"Custom","prtg.standardlookups.hyperv.communicationstate");
- "CpuUtilization" = @($VMHost.CpuUtilization,"CPU",0,1,1,0);
- "TotalMemory" = @($VMHost.TotalMemory,"BytesMemory",0,1,0,"Kilobyte");
- "AvailableMemory" = @(($VMHost.AvailableMemory * 1024 * 1024),"BytesMemory",0,1,0,"MegaByte");
- "ClusterNodeStatus" = @($VMHost.ClusterNodeStatus,"Custom","prtg.standardlookups.hyperv.clusternodestatus");
- "VirtualServerState" = @($VMHost.VirtualServerState,"Custom","prtg.standardlookups.hyperv.virtualserverstate");
- "ComputerState" = @($VMHost.ComputerState,"Custom","prtg.standardlookups.hyperv.computerstate");
- "HostCluster" = @($VMHost.HostCluster,"Custom","prtg.standardlookups.hyperv.clusternodestatus"); }
- # first, we'll format the data and sanatize the metrics
- $channels = (This-PrtgXmlWrapper -Metrics $metrics);
- # ...then we'll push it to PRTG. Don't forget to join the array!
- This-PushData -token $VMHost.id -data $channels;
- }
- # The same thing again, but for the VMs
- foreach($VM in $VMs){
- $metrics = @{
- "VMState" = @($VM.Status,"Custom","prtg.standardlookups.hyperv.vmstatus",0,"<NotifyChanged>");
- "CPU Usage" = @($VM.PerfCPUUtilization,"Percent",0,0,0,0);
- "PerfDiskBytesRead" = @($VM.PerfDiskBytesRead,"BytesDisk",0,0,0,0);
- "PerfDiskBytesWrite" = @($VM.PerfDiskBytesWrite,"BytesDisk",0,0,0,0); }
- # first, we'll format the data and sanatize the metrics ...
- $channels = (This-PrtgXmlWrapper -Metrics $metrics);
- # ...then we'll push it to PRTG.
- This-PushData -token $VM.id -data $channels;
- }
- }
- #
- This-GetSCVMMObjects;
- $objects = (((gc $guidFile) | ? {$_.trim() -ne "" } | Measure-Object).Count)
- write-host ([string]::Format("{0}:Monitoring {1} objects and added {0} objects.",$global:counter,$objects));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement