{"id":5726,"date":"2020-10-12T08:30:23","date_gmt":"2020-10-12T06:30:23","guid":{"rendered":"https:\/\/tekmart.co.za\/t-blog\/?p=5726"},"modified":"2020-10-12T08:31:17","modified_gmt":"2020-10-12T06:31:17","slug":"implement-simple-server-monitoring-with-powershell","status":"publish","type":"post","link":"https:\/\/tekmart.co.za\/t-blog\/implement-simple-server-monitoring-with-powershell\/","title":{"rendered":"How to implement simple server monitoring with PowerShell"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time-approximately:<\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<h2 class=\"wp-block-heading\"><strong>Administrators who build a server monitoring framework with PowerShell can develop their own customized checks for deeper insights into their environment.<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/cdn.ttgtmedia.com\/rms\/onlineImages\/bertram_adam.jpg\" alt=\"Adam Bertram\"\/><\/figure>\n\n\n\n<p>By <a href=\"https:\/\/www.techtarget.com\/contributor\/Adam-Bertram\">Adam Bertram<\/a><\/p>\n\n\n\n<p>As your server inventory expands, you will need assistance to ensure you can head off any problems.<\/p>\n\n\n\n<p>There are many server monitoring and reporting options in the market, but certain situations may call for a lightweight solution. Monitoring with PowerShell is a way to use\u00a0the native functionality in Windows\u00a0to create scripts that check your systems and send regular updates.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p>This article explores how to create a simple framework for Windows Server checks with the added benefit of generating reports at different intervals to assist with your monitoring efforts. While\u00a0PowerShell 7 is available, this tutorial is based on Windows PowerShell 5.1 due to the ease of its default PowerShell remoting setup process.<\/p><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Server monitoring with PowerShell<\/strong><\/h3>\n\n\n\n<p>While there are many server checks you can perform, this article will concentrate on just a few to demonstrate the capabilities of a simple PowerShell monitoring framework. This tutorial will cover:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Disk space.<\/strong>\u00a0Checking local disks and triggering a warning if the percentage of free space goes below 10%.<\/li><li><strong>OS version.<\/strong>\u00a0Checking if the version number is lower than 6.2. If it is, then the server OS predates Windows Server 2012, 2016 and 2019 and is no longer supported by Microsoft.<\/li><li><strong>Expiring certificates.<\/strong>\u00a0Compile a list of\u00a0certificates that are within 30 days of expiration.<\/li><li><strong>License usage.<\/strong>\u00a0Check licensed states and report any that are unlicensed.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Script structure for server monitoring with PowerShell<\/strong><\/h3>\n\n\n\n<p>The overall idea behind this server monitoring framework is to run a series of checks on one or more servers, save the results and then review the findings. To do this, we will\u00a0create functions in the PowerShell script.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Invoke-ServerCheck.<\/strong>&nbsp;This function will take in a series of checks and computers to run those scripts against, then export the results to an XML file.<\/li><li><strong>New-ServerReport<\/strong><strong>.<\/strong>&nbsp;This function will take the XML files and generate different formats based on the requested report type, either daily or monthly.<\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Constructing the server checks<\/h3>\n\n\n\n<p>There are many ways to structure a potential script to do server checks, but this tutorial will place all checks in a hashtable that uses scriptblocks, which makes it easy to run the code on the requested computers, either local or remotely.<\/p>\n\n\n\n<p>Additionally, the script uses a&nbsp;<strong>condition<\/strong>&nbsp;key and associated scriptblock to report whether the check has passed or failed. Every condition should return a Boolean value.<\/p>\n\n\n\n<p>Adding new checks is as easy as adding a new top-level key with a sub-key of&nbsp;<strong>check<\/strong>&nbsp;and&nbsp;<strong>condition<\/strong>. Both are scriptblocks run by&nbsp;<strong>Invoke-Command<\/strong>&nbsp;within the&nbsp;<strong>Invoke-ServerCheck&nbsp;<\/strong>function.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">$Checks = @{<br> &nbsp; 'OSVersion' = @{<br> &nbsp;&nbsp;&nbsp; 'Check' = {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, ServicePackMajorVersion, OSArchitecture<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; 'Condition' = {$_.Version -LT 6.2}<br> &nbsp; }<br> &nbsp; 'Certificates' = @{<br> &nbsp;&nbsp;&nbsp; 'Check' = {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Get-ChildItem -Path 'Cert:' -Recurse -ExpiringInDays 30 | Select-Object Subject, NotAfter<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; 'Condition' = {$Result.Count -GT 0}<br> &nbsp; }<br> &nbsp; 'DiskSpace' = @{<br> &nbsp;&nbsp;&nbsp; 'Check' = {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Get-CIMInstance -Class 'Win32_logicaldisk' -Filter \"DriveType = '3'\" | Select-Object -Property DeviceID, @{L='FreeSpaceGB';E={\"{0:N2}\" -f ($_.FreeSpace \/1GB)}}, @{L=\"Capacity\";E={\"{0:N2}\" -f ($_.Size\/1GB)}}<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; 'Condition' = { ($Result | Where-Object { (($_.FreeSpaceGB \/ $_.Capacity) * 100) -LT 10 }).Count -GT 0 }<br> &nbsp; }<br> &nbsp; 'License' = @{<br> &nbsp;&nbsp;&nbsp; 'Check' = {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Enum Licensestatus {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Unlicensed&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 0<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Licensed&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 1<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OOBGrace&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 2<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; OOTGrace&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 3<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NonGenuineGrace = 4<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Notification&nbsp;&nbsp;&nbsp; = 5<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ExtendedGrace&nbsp;&nbsp; = 6<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Get-CimInstance -ClassName SoftwareLicensingProduct -Filter \"PartialProductKey IS NOT NULL\" | Select-Object Name, ApplicationId, @{N='LicenseStatus'; E={[LicenseStatus]$_.LicenseStatus} }<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Condition' = {($Results | Where-Object LicenseStatus -NE 'Licensed').Count -EQ 0}<br> &nbsp; }&nbsp; <br> }<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How the Invoke-ServerCheck function works<\/strong><\/h3>\n\n\n\n<p>The&nbsp;<strong>Invoke-ServerCheck<\/strong>&nbsp;function handles the bulk of the server monitoring with PowerShell. This function takes in an array of checks from the&nbsp;<strong>$Checks<\/strong>&nbsp;variable, then each set of checks will be run across the servers or the local computer.<\/p>\n\n\n\n<p>The script executes the following steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>iterates over each check in the\u00a0<strong>$Checks<\/strong>\u00a0variable;<\/li><li>runs\u00a0<strong>Invoke-Command<\/strong>\u00a0on the scriptblock from the\u00a0<strong>Checks<\/strong>\u00a0key;<\/li><li>stores the result in a\u00a0<strong>$CheckResults<\/strong>\u00a0variable; and<\/li><li>saves the XML\u00a0of the\u00a0<strong>$Output<\/strong>\u00a0variable to the requested path, which allows for easier manipulation of this variable in later functions.<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">Function Invoke-ServerCheck {<br> &nbsp; [CmdletBinding()]<br> <br> &nbsp; Param(<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 0, Mandatory = $True)]$Checks,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 1, ValueFromPipeline = $True)]$ComputerName,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 2)]$Path = $Env:TEMP<br> &nbsp; )<br> <br> &nbsp; Process {<br> &nbsp;&nbsp;&nbsp; If ($ComputerName) {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Computer = $ComputerName<br> &nbsp;&nbsp;&nbsp; } Else {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Computer = $Env:COMPUTERNAME<br> &nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp; $CheckResults = @()<br> <br> &nbsp;&nbsp;&nbsp; $Checks.GetEnumerator() | ForEach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;Write-Host \"Running Check, $($_.Key), on $Computer\" -ForegroundColor 'Green'<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Params = @{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"ScriptBlock\"&nbsp; = $_.Value.Check<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Verbose\"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = $MyInvocation.BoundParameters.Verbose<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If ($ComputerName) {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Params.Add('ComputerName', $Computer)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Result = Invoke-Command @Params<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $CheckResults += ,[PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Check\"&nbsp;&nbsp;&nbsp;&nbsp; = $_.Key <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Result\"&nbsp;&nbsp;&nbsp; = $Result<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Condition\" = (Invoke-Command -ScriptBlock $_.Value.Condition -ArgumentList $Result)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp; $Output = [PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Server\"&nbsp; = $Computer<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Results\" = $CheckResults<br> &nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp; $FileName = \"ServerResults-{0}-{1}.xml\" -F $Computer, (Get-Date -Format \"yyyy_MM_dd_HH_mm_ss\")<br> <br> &nbsp;&nbsp;&nbsp; Export-Clixml -Path (Join-Path -Path $Path -ChildPath $FileName) -InputObject $Output<br> &nbsp; }<br> }<\/pre>\n\n\n\n<p>Next, we will want to generate a report telling which checks have passed or failed for any of the given servers.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>How the New-ServerReport function operates<\/strong><\/h3>\n\n\n\n<p>To make better sense of which checks have run against which servers, we will use the&nbsp;<strong>New-ServerReport<\/strong>&nbsp;function.<\/p>\n\n\n\n<p>The function performs the following steps:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>looks for XML files that match the name&nbsp;<strong>ServerResults<\/strong>;<\/li><li>runs checks based on whether a&nbsp;<strong>Daily<\/strong>&nbsp;or&nbsp;<strong>Monthly<\/strong>&nbsp;report exists;<\/li><li>looks at the&nbsp;<strong>CreationTime<\/strong>&nbsp;to determine whether to either pull all files on the given day or 30 days back;<\/li><li>after gathering the results, groups them based on the servers and then outputs a table of the checks; and<\/li><li>saves the results to a CSV file for later viewing.<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">Function New-ServerReport {<br> &nbsp; [CmdletBinding()]<br> <br> &nbsp; Param(<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 0)]<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [ValidateSet('Daily','Monthly')]<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [String]$Type = 'Daily',<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 1)]$Path = $Env:TEMP,<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [Parameter(Position = 2)]$ReportPath = $Env:TEMP<br> &nbsp; )<br> <br> &nbsp; Process {<br> &nbsp;&nbsp;&nbsp; $Files = Get-ChildItem -Path $Path -Filter '*.xml' | Where-Object Name -Match 'ServerResults'<br> <br> &nbsp;&nbsp;&nbsp; Switch ($Type) {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Daily' {&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Results = $Files | Where-Object 'CreationTime' -GT (Get-Date -Hour 0 -Minute 00 -Second 00)<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ResultArray = @()<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Results | ForEach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ResultArray += ,[PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Results'&nbsp; = (Import-Clixml -Path $_.FullName)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DateTime' = $_.CreationTime<br> &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report = $ResultArray | Foreach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $DateTime = $_.DateTime<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Results | Group-Object -Property 'Server' | Foreach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Server = $_.Name<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Group.Results | ForEach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Object = [PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Server\"&nbsp;&nbsp; = $Server<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Check\"&nbsp;&nbsp;&nbsp; = $_.Check<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Result\"&nbsp;&nbsp; = $_.Condition<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"DateTime\" = $DateTime<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Object<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $FileName = \"ServersReport-{0}.csv\" -F (Get-Date -Format \"yyyy_MM_dd_HH_mm_ss\")<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report | Export-CSV -Path (Join-Path -Path $ReportPath -ChildPath $FileName) -NoTypeInformation<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Break<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Monthly' {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Results = $Files | Where-Object 'CreationTime' -GT (Get-Date).AddDays(-30)<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ResultArray = @()<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Results | ForEach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ResultArray += ,[PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'Results'&nbsp; = (Import-Clixml -Path $_.FullName)<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'DateTime' = $_.CreationTime<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report = $ResultArray | Foreach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $DateTime = $_.DateTime<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Results | Group-Object -Property 'Server' | Foreach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Server = $_.Name<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Group.Results | ForEach-Object {<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Object = [PSCustomObject]@{<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Server\"&nbsp;&nbsp; = $Server<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;\"Check\"&nbsp;&nbsp;&nbsp; = $_.Check<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"Result\"&nbsp;&nbsp; = $_.Condition<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \"DateTime\" = $DateTime<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Object<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $FileName = \"ServersReport-{0}.csv\" -F (Get-Date -Format \"yyyy_MM_dd_HH_mm_ss\")<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report | Export-CSV -Path (Join-Path -Path $ReportPath -ChildPath $FileName) -NoTypeInformation<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $Report<br> <br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Break<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br> &nbsp;&nbsp;&nbsp; }<br> &nbsp; }<br> }<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Running the monitoring with PowerShell script<\/strong><\/h3>\n\n\n\n<p>There are several ways to use this script, but the simplest is to add the servers to check and then use the&nbsp;<strong>New-ServerReport<\/strong>&nbsp;command to determine which checks have run over time.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"># Perform Checks on Requested Servers<br> @(\"Server1\",\"Server2\",\"Server3\") | Invoke-ServerCheck<br> # Generate Daily Report<br> New-ServerReport<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Modular structure provides flexibility when monitoring with PowerShell<\/strong><\/h3>\n\n\n\n<p>By using PowerShell to create a modular framework to easily create and execute checks on servers, a system administrator can quickly gain better visibility and control of their environment. Although these checks are simple, there are many ways to extend the capabilities to include more in-depth inquiries into your server inventory.<\/p>\n\n\n\n<p>With new\u00a0cross-platform abilities of PowerShell 7, you can extend these checks to work on Linux systems to handle things such as necessary OS-specific updates.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time-approximately:<\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>Administrators who build a server monitoring framework with PowerShell can develop their own customized checks for deeper insights into their environment. By Adam Bertram As your server inventory expands, you will need assistance to ensure you can head off any problems. There are many server monitoring and reporting options in the market, but certain situations may call for a lightweight<\/p>\n<p><a class=\"more-link\" href=\"https:\/\/tekmart.co.za\/t-blog\/implement-simple-server-monitoring-with-powershell\/\">Read More<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,5,137,30,16,3,12,95],"tags":[],"class_list":["post-5726","post","type-post","status-publish","format-standard","hentry","category-datacenter-news","category-engage-the-experts","category-enterprise-infrastructure-management","category-expert-advise-and-opinion","category-how-tos-and-other-useful-tips-and-tricks","category-industry-news-and-expert-advise","category-tekmart-enterprise-hardware-tips","category-timeless-tips"],"_links":{"self":[{"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/posts\/5726","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/comments?post=5726"}],"version-history":[{"count":2,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/posts\/5726\/revisions"}],"predecessor-version":[{"id":5728,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/posts\/5726\/revisions\/5728"}],"wp:attachment":[{"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/media?parent=5726"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/categories?post=5726"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tekmart.co.za\/t-blog\/wp-json\/wp\/v2\/tags?post=5726"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}