Author Posts

January 1, 2012 at 12:00 am

by nathanburgess at 2012-09-14 08:55:49

I am very new to scripting but how found it to be a great way to accomplish tasks and get reports. My issue is I have this uptime script that I found that works great in getting real percentage up time reporting. I would love to figure out how to add it to the morning report. Below is the code for the uptime reporting, it uses the event logs to work out better percentages. I have to submit weekly reports and being able to set date ranges will be a big help. I am so glad I ran across these forums.

#—————————————————————
# CalculateSystemUpTimeFromEventLog.ps1
# ed wilson, msft, 9/6/2008
#
# Creates a system.TimeSpan object to subtract date values
# Uses a .NET Framework class, system.collections.sortedlist to sort the events from eventlog.
#
#—————————————————————
#Requires -version 2.0
Param($NumberOfDays = 30, [switch]$debug)

if($debug) { $DebugPreference = " continue" }

[timespan]$uptime = New-TimeSpan -start 0 -end 0
$currentTime = get-Date
$startUpID = 6005
$shutDownID = 6006
$minutesInPeriod = (24*60)*$NumberOfDays
$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

Write-debug "'$uptime $uptime" ; start-sleep -s 1
write-debug "'$currentTime $currentTime" ; start-sleep -s 1
write-debug "'$startingDate $startingDate" ; start-sleep -s 1

$events = Get-EventLog -LogName system |
Where-Object { $_.eventID -eq $startUpID -OR $_.eventID -eq $shutDownID `
-and $_.TimeGenerated -ge $startingDate }

write-debug "'$events $($events)" ; start-sleep -s 1

$sortedList = New-object system.collections.sortedlist

ForEach($event in $events)
{
$sortedList.Add( $event.timeGenerated, $event.eventID )
} #end foreach event
$uptime = $currentTime – $sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

For($item = $sortedList.Count-2 ; $item -ge 0 ; $item — )
{
Write-Debug "$item `t `t $($sortedList.GetByIndex($item)) `t `
$($sortedList.Keys[$item])"
if($sortedList.GetByIndex($item) -eq $startUpID)
{
$uptime += ($sortedList.Keys[$item+1] – $sortedList.Keys[$item])
Write-Debug "adding uptime. `t uptime is now: $uptime"
} #end if
} #end for item

"Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
$uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 – ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 – $percentDowntime

"$percentDowntime% downtime and $percentUpTime% uptime."

by DonJ at 2012-09-14 09:09:01

So, what exactly do you need help with?

by nathanburgess at 2012-09-14 10:46:00

I am finding it difficult melding the script in my post with the morning report script. The script in my post gives more detail. I am sure after about another month or so playing around and experimenting I might get it. Thought I would post to get some ideas, I am very new to powershell. The two scripts give me the reporting I need just would like to combine and I am struggling with figuring that out.

by DonJ at 2012-09-14 10:53:29

Sure, happy to help! Is there some particular thing you can point to that you're having difficulty with, and we could start there? I'm just not sure what direction to go in terms of offering suggestions... maybe focus on how this script is getting its data, so that you can add something similar to your own script?

by nathanburgess at 2012-09-14 11:51:11

Basically I want to replace the uptime in this script below. And I think the only way I am going to accomplish that is to start with the uptime script in my original post and work through each component. It will probably take me a number of weeks to accomplish as I am learning, I am up for the challenge I think I will start with the uptime script above, get to work remotely against a list of servers from a txt file and produce a nicely charted report.

# PowerShell Systems Report
# Example usage: .\SystemsReport.ps1 .\list.txt
# Remember that list.txt is the file containing a list of Server names to run this against

#region Variables and Arguments
$users = "nathan@abwe.org" # List of users to email your report to (separate by comma)
$fromemail = "its@abwe.org"
$server = "mordakhub.abwe.org" #enter your own SMTP server DNS name / IP address here
$list = $args[0] #This accepts the argument you add to your scheduled task for the list of servers. i.e. list.txt
$computers = get-content $list #grab the names of the servers/computers to check from the list.txt file.
# Set free disk space threshold below in percent (default at 10%)
$thresholdspace = 20
[int]$EventNum = 3
[int]$ProccessNumToFetch = 10
$ListOfAttachments = @()
$Report = @()
$CurrentTime = Get-Date
#endregion

Function Create-PieChart() {
param([string]$FileName)

[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")

#Create our chart object
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Width = 300
$Chart.Height = 290
$Chart.Left = 10
$Chart.Top = 10

#Create a chartarea to draw on and add this to the chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Chart.ChartAreas.Add($ChartArea)
[void]$Chart.Series.Add("Data")

#Add a datapoint for each value specified in the arguments (args)
foreach ($value in $args[0]) {
Write-Host "Now processing chart value: " + $value
$datapoint = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $value)
$datapoint.AxisLabel = "Value" + "(" + $value + " GB)"
$Chart.Series["Data"].Points.Add($datapoint)
}

$Chart.Series["Data"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Pie
$Chart.Series["Data"]["PieLabelStyle"] = "Outside"
$Chart.Series["Data"]["PieLineColor"] = "Black"
$Chart.Series["Data"]["PieDrawingStyle"] = "Concave"
($Chart.Series["Data"].Points.FindMaxByValue())["Exploded"] = $true

#Set the title of the Chart to the current date and time
$Title = new-object System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add($Title)
$Chart.Titles[0].Text = "RAM Usage Chart (Used/Free)"

#Save the chart to a file
$Chart.SaveImage($FileName + ".png","png")
}

Function Get-HostUptime {
param ([string]$ComputerName)
$Uptime = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName
$LastBootUpTime = $Uptime.ConvertToDateTime($Uptime.LastBootUpTime)
$Time = (Get-Date) – $LastBootUpTime
Return '{0:00} Days, {1:00} Hours, {2:00} Minutes, {3:00} Seconds' -f $Time.Days, $Time.Hours, $Time.Minutes, $Time.Seconds
}

# Assemble the HTML Header and CSS for our Report
$HTMLHeader = @"
< !DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"&gt;
My Systems Report


"@

foreach ($computer in $computers) {

$DiskInfo= Get-WMIObject -ComputerName $computer Win32_LogicalDisk | Where-Object{$_.DriveType -eq 3} | Where-Object{ ($_.freespace/$_.Size)*100 -lt $thresholdspace} `
| Select-Object SystemName, DriveType, VolumeName, Name, @{n='Size (GB)';e={"{0:n2}" -f ($_.size/1gb)}}, @{n='FreeSpace (GB)';e={"{0:n2}" -f ($_.freespace/1gb)}}, @{n='PercentFree';e={"{0:n2}" -f ($_.freespace/$_.size*100)}} | ConvertTo-HTML -fragment

#region System Info
$OS = (Get-WmiObject Win32_OperatingSystem -computername $computer).caption
$SystemInfo = Get-WmiObject -Class Win32_OperatingSystem -computername $computer | Select-Object Name, TotalVisibleMemorySize, FreePhysicalMemory
$TotalRAM = $SystemInfo.TotalVisibleMemorySize/1MB
$FreeRAM = $SystemInfo.FreePhysicalMemory/1MB
$UsedRAM = $TotalRAM – $FreeRAM
$RAMPercentFree = ($FreeRAM / $TotalRAM) * 100
$TotalRAM = [Math]::Round($TotalRAM, 2)
$FreeRAM = [Math]::Round($FreeRAM, 2)
$UsedRAM = [Math]::Round($UsedRAM, 2)
$RAMPercentFree = [Math]::Round($RAMPercentFree, 2)
#endregion

$TopProcesses = Get-Process -ComputerName $computer | Sort WS -Descending | Select ProcessName, Id, WS -First $ProccessNumToFetch | ConvertTo-Html -Fragment

#region Services Report
$ServicesReport = @()
$Services = Get-WmiObject -Class Win32_Service -ComputerName $computer | Where {($_.StartMode -eq "Auto") -and ($_.State -eq "Stopped")}

foreach ($Service in $Services) {
$row = New-Object -Type PSObject -Property @{
Name = $Service.Name
Status = $Service.State
StartMode = $Service.StartMode
}

$ServicesReport += $row

}

$ServicesReport = $ServicesReport | ConvertTo-Html -Fragment
#endregion

#region Event Logs Report
$SystemEventsReport = @()
$SystemEvents = Get-EventLog -ComputerName $computer -LogName System -EntryType Error,Warning -Newest $EventNum
foreach ($event in $SystemEvents) {
$row = New-Object -Type PSObject -Property @{
TimeGenerated = $event.TimeGenerated
EntryType = $event.EntryType
Source = $event.Source
Message = $event.Message
}
$SystemEventsReport += $row
}

$SystemEventsReport = $SystemEventsReport | ConvertTo-Html -Fragment

$ApplicationEventsReport = @()
$ApplicationEvents = Get-EventLog -ComputerName $computer -LogName Application -EntryType Error,Warning -Newest $EventNum
foreach ($event in $ApplicationEvents) {
$row = New-Object -Type PSObject -Property @{
TimeGenerated = $event.TimeGenerated
EntryType = $event.EntryType
Source = $event.Source
Message = $event.Message
}
$ApplicationEventsReport += $row
}

$ApplicationEventsReport = $ApplicationEventsReport | ConvertTo-Html -Fragment
#endregion

# Create the chart using our Chart Function
Create-PieChart -FileName ((Get-Location).Path + "\chart-$computer") $FreeRAM, $UsedRAM
$ListOfAttachments += "chart-$computer.png"
#region Uptime
# Fetch the Uptime of the current system using our Get-HostUptime Function.
$SystemUptime = Get-HostUptime -ComputerName $computer
#endregion

# Create HTML Report for the current System being looped through
$CurrentSystemHTML = @"


$computer Report

System Info

System Uptime $SystemUptime
OS $OS
Total RAM (GB) $TotalRAM
Free RAM (GB) $FreeRAM
Percent free RAM $RAMPercentFree

"$computer

Disk Info

Drive(s) listed below have less than $thresholdspace % free space. Drives above this threshold will not be listed.

$DiskInfo

System Processes – Top $ProccessNumToFetch Highest Memory Usage

The following $ProccessNumToFetch processes are those consuming the highest amount of Working Set (WS) Memory (bytes) on $computer

$TopProcesses

System Services – Automatic Startup but not Running

The following services are those which are set to Automatic startup type, yet are currently not running on $computer

$ServicesReport

Events Report – The last $EventNum System/Application Log Events that were Warnings or Errors

The following is a list of the last $EventNum System log events that had an Event Type of either Warning or Error on $computer

$SystemEventsReport

The following is a list of the last $EventNum Application log events that had an Event Type of either Warning or Error on $computer

$ApplicationEventsReport

"@
# Add the current System HTML Report into the final HTML Report body
$HTMLMiddle += $CurrentSystemHTML

}

# Assemble the closing HTML for our report.
$HTMLEnd = @"



"@

# Assemble the final report from all our HTML sections
$HTMLmessage = $HTMLHeader + $HTMLMiddle + $HTMLEnd
# Save the report out to a file in the current path
$HTMLmessage | Out-File ((Get-Location).Path + "\report.html")
# Email our report out
send-mailmessage -from $fromemail -to $users -subject "Systems Report" -Attachments $ListOfAttachments -BodyAsHTML -body $HTMLmessage -priority Normal -smtpServer $server

by DonJ at 2012-09-14 14:07:01

Wow, lotta code.

So, I think you'd benefit from some modularization. Take each information-retrieving piece and turn it into a function. Then, in the main script, you'd call the functions and display their output.

For example:

(Notice that I'm removing the "debug" switch and properly implementing PowerShell's built-in support for that; the function can still be called with -Debug to enable debug output.

function Get-UpTime {
[CmdletBinding()]
Param($NumberOfDays = 30)

[timespan]$uptime = New-TimeSpan -start 0 -end 0
$currentTime = get-Date
$startUpID = 6005
$shutDownID = 6006
$minutesInPeriod = (24*60)*$NumberOfDays
$startingDate = (Get-Date -Hour 00 -Minute 00 -Second 00).adddays(-$numberOfDays)

Write-debug "'$uptime $uptime" ; start-sleep -s 1
write-debug "'$currentTime $currentTime" ; start-sleep -s 1
write-debug "'$startingDate $startingDate" ; start-sleep -s 1

$events = Get-EventLog -LogName system |
Where-Object { $_.eventID -eq $startUpID -OR $_.eventID -eq $shutDownID `
-and $_.TimeGenerated -ge $startingDate }

write-debug "'$events $($events)" ; start-sleep -s 1

$sortedList = New-object system.collections.sortedlist

ForEach($event in $events)
{
$sortedList.Add( $event.timeGenerated, $event.eventID )
} #end foreach event
$uptime = $currentTime - $sortedList.keys[$($sortedList.Keys.Count-1)]
Write-Debug "Current uptime $uptime"

For($item = $sortedList.Count-2 ; $item -ge 0 ; $item -- )
{
Write-Debug "$item `t `t $($sortedList.GetByIndex($item)) `t `
$($sortedList.Keys[$item])"
if($sortedList.GetByIndex($item) -eq $startUpID)
{
$uptime += ($sortedList.Keys[$item+1] - $sortedList.Keys[$item])
Write-Debug "adding uptime. `t uptime is now: $uptime"
} #end if
} #end for item

$output = "Total up time on $env:computername since $startingDate is " + "{0:n2}" -f `
$uptime.TotalMinutes + " minutes."
$UpTimeMinutes = $Uptime.TotalMinutes
$percentDownTime = "{0:n2}" -f (100 - ($UpTimeMinutes/$minutesInPeriod)*100)
$percentUpTime = 100 - $percentDowntime

$output += "$percentDowntime% downtime and $percentUpTime% uptime."
Write-Output $output
}

Get-UpTime

Notice that, inside the function, I've put the output into the $output variable. At the end of the function, I write that to the Output stream (I could have also written return $output to get the same effect). In the main script, simply calling Get-Uptime runs the function and displays its output.

This approach would make it a lot easier to continually expand your report and add more information to it, or to easily improve individual pieces. I know you already had a Get-HostUptime function; the function I've given you here could just replace that.

You might also want to look into the ConvertTo-HTML cmdlet. You're doing a lot of manual HTML creation that PowerShell'd be willing to for ya. For example, taking this from your current script:


#region System Info
$OS = (Get-WmiObject Win32_OperatingSystem -computername $computer).caption
$SystemInfo = Get-WmiObject -Class Win32_OperatingSystem -computername $computer | Select-Object Name, TotalVisibleMemorySize, FreePhysicalMemory
$TotalRAM = $SystemInfo.TotalVisibleMemorySize/1MB
$FreeRAM = $SystemInfo.FreePhysicalMemory/1MB
$UsedRAM = $TotalRAM - $FreeRAM
$RAMPercentFree = ($FreeRAM / $TotalRAM) * 100
$TotalRAM = [Math]::Round($TotalRAM, 2)
$FreeRAM = [Math]::Round($FreeRAM, 2)
$UsedRAM = [Math]::Round($UsedRAM, 2)
$RAMPercentFree = [Math]::Round($RAMPercentFree, 2)
#endregion

I might rewrite that as:

$OS_HTML = Get-WmiObject Win32_OperartingSystem -computername $computername |
Select-Object -properties @{label='OS';expression={$_.caption}},
TotalVisibleMemorySize,FreePhysicalMemory,
@{label='TotalRAM';expression={$_.TotalVisibleMemorySize / 1 MB}},
@{label='FreeRAM';expression={$_.FreePhysicalMemory / 1 MB}},
@{label='UsedRAM';expression={$_.TotalVisibleMemorySize - $_.FreePhysicalMemory / 1MB -as [int]}} |
ConvertTo-HTML -Fragment -Body '

System Information

' -As List

I left off some of the other properties your script was including, just for clarity – you'd probably want to add them back in. This approach will give you an HTML fragment in $OS_HTML; you can just concatenate that into the main body of an HTML page, for example. PowerShell can also make tables, which is the default – I used -As list to make a list, though. It'll handle all the formatting that way. This approach also avoid the creation of a lot of extra variables, which I like.

Anyway... just a couple of suggestions.