PowerShell - Export Mailbox Statistics to HTML Report - powershell

I'm trying to generate a general system report that pulls basic statistics for network hosts. My script works as expected up until the "o365ExchConn" function, whereby it pipes all of the "Import-Pssession" output into the variable that's used to generate the HTML fragment at the end of the script.
How do I exclude this output? I just require the statistics specified in the array within the function. (i.e. Displayname, TotalItemsize & TotalItemCount).
$computername = Read-Host -Prompt "Enter Hostname"
$path = Read-Host -Prompt "Enter path for HTML report output (e.g. C:\Temp)"
$date = Get-Date
function Get-CSInfo {
param($computername)
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
$cs = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
$props = #{‘Hostname’=$computername
‘OS Version’=$os.version
‘OS Build’=$os.buildnumber
‘Service Pack’=$os.sevicepackmajorversion;
'OS Name'=$os.caption}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
function Get-RamInfo {
$os = Get-WmiObject -class Win32_OperatingSystem -ComputerName $ComputerName
$cs = Get-WmiObject -class Win32_ComputerSystem -ComputerName $ComputerName
$props = #{'RAM (GB) - Total Available'="{0:N2}" -f ($cs.totalphysicalmemory / 1GB);
'RAM (GB) - Free for Use'="{0:N2}" -f ($os.freephysicalmemory / 1MB);
'RAM (GB) - Currently in Use'="{0:N2}" -f ($cs.totalphysicalmemory / 1GB) - ($os.freephysicalmemory / 1MB)}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
function Get-InfoDisk {
param($computername)
$drives = Get-WmiObject -class Win32_LogicalDisk -ComputerName $ComputerName -Filter "DriveType=3"
foreach ($drive in $drives) {
$props = #{'Drive'=$drive.DeviceID;
'Size'=$drive.size / 1GB -as [int];
'Free'="{0:N2}" -f ($drive.freespace / 1GB);
'FreePct'=$drive.freespace / $drive.size * 100 -as [int]}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
}
function Get-InfoBadService {
$svcs = Get-WmiObject -class Win32_Service -ComputerName $ComputerName -Filter "StartMode='Auto' AND State<>'Running'"
foreach ($svc in $svcs) {
$props = #{'ServiceName'=$svc.name;
'LogonAccount'=$svc.startname;
'DisplayName'=$svc.displayname}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}}
function Get-EventLogs {
$time = (Get-Date) - (New-TimeSpan -Day 1)
$logs = Get-WinEvent -ComputerName $ComputerName -FilterHashTable #{LogName='Application', 'System'; Level=1,2,3; StartTime=$time}
foreach ($log in $logs) {
$props = #{'Provider Name'=$log.ProviderName;
'Timestamp'=$log.TimeCreated;
'Id'=$log.Id;
'Error Type'=$log.LevelDisplayName;
'Message'=$log.Message;
}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
}
function Get-SecurityLogs {
$time = (Get-Date) - (New-TimeSpan -Day 1)
$sec_logs = Get-EventLog -ComputerName $computername -LogName Security -EntryType FailureAudit -After $time
foreach ($sec_log in $sec_logs) {
$props = #{'Timestamp'=$sec_log.TimeGenerated;
'Event ID'=$sec_log.EventID;
'Error Type'=$sec_log.EntryType;
'Message'=$sec_log.Message;
}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
}
function O365ExchConn {
$credentials = Get-Credential
Write-Output "Creating remote PowerShell session ..."
$session = New-PSSession -ConfigurationName Microsoft.Exchange -Credential $credentials -ConnectionUri https://outlook.office365.com/powershell-liveid?DelegatedOrg=*ENTERCOMPANYNAMETENANTNAME* -Authentication Basic -AllowRedirection
Write-Output "Importing PowerShell session ..."
Import-PSSession $session -AllowClobber
$userlist = Get-Mailbox | Get-MailboxStatistics | ft displayname, totalitemsize, itemcount
foreach($user in $userlist){
$props = #{'DisplayName'=$user.displayname;
'Total Size'=$user.totalitemsize;
'Total Items'=$user.itemcount;
}
$obj = New-Object -TypeName PSObject -Property $props
Write-Output $obj
}
}
$frag1 = Get-CSInfo –computername $computername |
ConvertTo-Html -As LIST -Fragment -PreContent ‘<h2>System Info</h2>’ |
Out-String
$frag2 = Get-InfoDisk -ComputerName $computername |
ConvertTo-Html -As Table -Fragment -PreContent '<h2>Disk Info</h2>' |
Out-String
$frag3 = Get-RamInfo -ComputerName $computername |
ConvertTo-Html -As Table -Fragment -PreContent '<h2>Memory Info</h2>' |
Out-String
$frag4 = Get-InfoBadService -ComputerName $computername |
ConvertTo-Html -As Table -Fragment -PreContent '<h2>Stopped Services</h2>' |
Out-String
$frag5 = Get-EventLogs -ComputerName $computername |
ConvertTo-Html -As Table -Fragment -PreContent '<h2>Application / System Logs</h2>' |
Out-String
$frag6 = Get-SecurityLogs -ComputerName $computername |
ConvertTo-Html -As Table -Fragment -PreContent '<h2>Security Logs</h2>' |
Out-String
$frag7 = O365ExchConn -ComputerName $computername |
ConvertTo-Html -As List -Fragment -PreContent '<h2>Exchange Mailbox Stats</h2>' |
Out-String
$head = #’
<style>
body { background-color:#dddddd;
font-family:Tahoma;
font-size:12pt; }
td, th { border:1px solid black;
border-collapse:collapse; }
th { color:white;
background-color:black; }
table, tr, td, th { padding: 2px; margin: 0px }
table { margin-left:50px; }
</style>
‘#
$filepath = Join-Path -Path $path -ChildPath "$computername.html"
ConvertTo-HTML -head $head -PostContent $frag1,$frag2,$frag3,$frag4,$frag5,$frag6,$frag7 -PreContent “<h1>System Maintenance Report for $computername</h1><br><h2>Generated on $date” |
Out-File $filepath

There are multiple ways in PowerShell to prevent cmdlets from outputting text where it's not desired.
See this question for more information
[void] Import-PSSession $session -AllowClobber
Import-PSSession $session -AllowClobber | Out-Null
$ImportPSSession = Import-PSSession $session -AllowClobber

Related

How to run through all the hostnames in the selected file instead of just the first one?

My overall goal is to allow the user to have a gui pop-up which lets them select any txt file of choice and to use that file they selected to run the code below the function. As of right now, in a txt file that contains 50 targets, it only runs through the first PC name and completes the code.
What can I do to fix the issue?
function Get-Content($initialDirectory) {
[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
if ($initialDirectory) { $OpenFileDialog.initialDirectory = $initialDirectory }
$OpenFileDialog.filter = 'All files (*.*)|*.*'
[void] $OpenFileDialog.ShowDialog()
return $OpenFileDialog.FileName
}
($FilePermissions = Get-Content C:\)
###########################################################################
write-host "Please wait while gathering information..."
$counter = 0
foreach ($computernames in $FilePermissions)
{
Write-host "Processing $computernames ($counter/$($FilePermissions.count))"
IF (Test-Connection -BufferSize 32 -Count 1 -ComputerName $computernames -Quiet)
{
$Computersystem = Get-WMIObject Win32_ComputerSystem -ComputerName $computernames -AsJob
$videocontroller = Get-WmiObject win32_videocontroller -ComputerName $computernames -AsJob
$nvidiacontroller = Get-WmiObject win32_videocontroller -ComputerName $computernames -AsJob
$bioscontroller1 = Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $computernames
$bioscontroller2 = Get-CimInstance -ClassName Win32_BIOS -ComputerName $computernames
$userlogon = Get-CimInstance -ClassName Win32_ComputerSystem -Property UserName -ComputerName $computernames
Wait-Job -Job $Computersystem,$videocontroller -Timeout 18 | out-Null
$computersystem_output = Receive-Job $Computersystem
$videocontroller_output = Receive-Job $videocontroller | ? {$_.name -ilike "*Intel*"}
$nvidiavideocontroller_output = Receive-Job $nvidiacontroller | ? {$_.name -ilike "*NVIDIA*"}
# Creating spreadsheet headers
$newrow = [Pscustomobject] #{
Host_name = $computersystem_output.name
Model_Name = $bioscontroller1.Model
Serial_Number = $bioscontroller2.SerialNumber
BIOS_Version = $bioscontroller2.SMBIOSBIOSVersion
Last_User_Logon = $userlogon.UserName
Intel_Card = $videocontroller_output.name
IntelGraphics_Version = $videocontroller_output.DriverVersion
Nvidia_Card = $nvidiavideocontroller_output.name
NvidiaGraphics_Version = $nvidiavideocontroller_output.DriverVersion
}
$newrow | export-csv -path c:\HostnameData.csv -Append -NoTypeInformation
Remove-Job -job $Computersystem,$videocontroller,$nvidiacontroller -Force
$counter++
}
Else
{
write-Host "The remote computer " $computernames " is Offline"
}
}

Export a custom object to a CSV file only gives me one computer

If I run this it works fine:
$Computers = get-content C:\POSH\test.txt
foreach ($comp in $Computers) {
$CS = Get-WmiObject -Class Win32_computersystem -ComputerName $comp
$Encl = Get-WmiObject -Class Win32_Systemenclosure -ComputerName $comp
$props = #{CompName=$cs.name
Manufacturer=$cs.manufacturer
Model=$cs.model
Serial=$encl.serialnumber}
$obj = New-Object -TypeName PSObject -property $props
Write-Output $obj
}
But if I change write-output $obj to $obj | export-csv c:\posh\test.csv
I only get one computer in the CSV file.
At a quick glance:
It seems you keep overwriting the object you make, by making a new object in each iteration.
You see all $computers on the screen because you use write-output before you repeat the task and overwrite the object. This becomes an issue once you want to combine them.
Otherwise more information might be helpful.
Hello. You can try this:
$obj | export-csv c:\posh\test.csv -Append
Or you can do try:
$Computers = get-content C:\POSH\test.txt
$Computers | % {
$CS = Get-WmiObject -Class Win32_computersystem -ComputerName $_
$Encl = Get-WmiObject -Class Win32_Systemenclosure -ComputerName $_
$obj = New-Object -TypeName psobject -Property #{
CompName=$cs.name
Manufacturer=$cs.manufacturer
Model=$cs.model
Serial=$encl.serialnumber
}
$obj1 = $obj | select -ExpandProperty CompName
$obj2 = $obj | select -ExpandProperty Manufacturer
$obj3 = $obj | select -ExpandProperty Model
$obj4 = $obj | select -ExpandProperty Serial
$out = $obj1 + ";" + $obj2 + ";" + $obj3 + ";" + $obj4
$out >> c:\posh\test.csv
}
$Computers = get-content C:\POSH\test.txt
$obj = foreach ($comp in $Computers) {
$CS = Get-WmiObject -Class Win32_computersystem -ComputerName $comp
$Encl = Get-WmiObject -Class Win32_Systemenclosure -ComputerName $comp
$props = #{CompName=$cs.name
Manufacturer=$cs.manufacturer
Model=$cs.model
Serial=$encl.serialnumber}
New-Object -TypeName PSObject -property $props
}
Write-Output $obj
You are overwriting the $obj variable on each iteration of the loop. So when you export it, it will always only contain the last computer in your list. You can create an array and add each object to it and then output the array to csv.
For example,
$Computers = get-content C:\POSH\test.txt
# Create array to hold all objects
$data = #()
foreach ($comp in $Computers) {
$CS = Get-WmiObject -Class Win32_computersystem -ComputerName $comp
$Encl = Get-WmiObject -Class Win32_Systemenclosure -ComputerName $comp
$props = #{CompName=$cs.name
Manufacturer=$cs.manufacturer
Model=$cs.model
Serial=$encl.serialnumber}
$obj = New-Object -TypeName PSObject -property $props
#Append the object to the array using +=
$data += $obj
}
#Export the objects which were stored in the data array
$data | Export-Csv C:\Test\output.csv -NoTypeInformation

new-object PSObject causes null-valued expression error

My PowerShell script:
Param([string]$Computers) #Must supply a comma seperated list of servers
$Threshold = 20 #Only show CPU over this number
$NoP = 20 #Number of processes to list
$NoRS = 4 #Number of result sets
If (! $Computers) {
Write-Host "Connection to server failed - please specify a server name." -ForegroundColor Red
Break
} Else {
$ComputerList = $Computers -Split " "#,[StringSplitOptions]'RemoveEmptyEntries')
}
$Credential = $host.ui.PromptForCredential("Need credentials", "Please enter your user name and password.", "", "NetBiosUserName")
If (! $Credential) {
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
$UserName = $Credential.Username
$Password = $Credential.GetNetworkCredential().Password
$CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
$Domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password)
If ($Domain.Name -eq $null){
Write-Host "Authentication failed - please verify your username and password." -ForegroundColor Red
Break
}
ForEach ($ComputerName In $ComputerList) {
$LoadPercentage = $Processors.LoadPercentage
If (!$LoadPercentage) {$LoadPercentage = 0}
Write-Host "Server: $ComputerName (CPU load $LoadPercentage%)" -NoNewline
$Processors = Get-WmiObject win32_processor -ComputerName $ComputerName -Credential $Credential
$i = 1
$TopProcess = #()
$PercentComplete = 0
Do{
$PercentComplete = [Math]::Floor($i/$NoRS*100)
Write-Progress -Activity $ComputerName -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
$ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process -ComputerName $ComputerName -Credential $Credential |
Select IDProcess,Name,PercentProcessorTime |
Where {$_.Name -ne "_Total" -and $_.Name -ne "Idle"} |
Sort PercentProcessorTime -Descending |
Select -First $NoP
ForEach ($Process In $ProcessList) {
$row = New-Object PSObject -Property #{
Id = $Process.IDProcess
Name = $Process.Name
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
CPU = $Process.PercentProcessorTime/$Processors.NumberOfLogicalProcessors -f {P}
Description = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).Description
}
$TopProcess += $row
}
$i++
} While ($i -lt $NoRS + 1)
Write-Progress -Activity $ComputerName -Completed
$Group = $TopProcess | Where {$_.CPU -gt $Threshold} | Group 'ID' | Where Count -eq $NoRS
If (!$Group) {
Write-Host " has no processes persistently above $Threshold percent CPU usage."
} Else {
$Processes = #()
ForEach ($Groupee In $Group) {
$Ungroup = $Groupee | Select -ExpandProperty Group
$CPU = 0
ForEach ($ugr in $Ungroup) {
$CPU += $ugr.CPU
}
$row = new-object PSObject -Property #{
Id = $Ungroup.Id | Select -First 1
Name = $Ungroup.Name | Select -First 1
CPU = $CPU/$NoRS
User = $Ungroup.User | Select -First 1
Description = $Ungroup.Description | Select -First 1
}
$Processes += $row
}
$Processes | Format-Table #{Expression={$_.User};Label="User Name";width=25},#{Expression={$_.CPU};Label="CPU";width=5},#{Expression={$_.Id};Label="ID";width=8},#{Expression={$_.Description};Label="Description";width=48}
}
}
intermittantly gives the following error:
You cannot call a method on a null-valued expression. At C:\Users\Jasons1\CPUusage.ps1:41 char:4
$row = new-object PSObject -Property #{
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : InvokeMethodOnNull
which I fail to understand as it is within a loop and should either work or get skipped as there is a test for null.
Pretty sure that your issues are stemming from this line:
User = (gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}).GetOwner().User
Specifically from .GetOwner(). Your where clause must not be finding a matching process for that item while it is in the loop. I realize there is not much time elapsed but WMI queries are not the fastest things out there.
What is happening is likely a result of a process queried earlier in $ProcessList = gwmi Win32_PerfFormattedData_PerfProc_Process and then later when you are using gwmi Win32_Process the list of processes changed. You need to account for this as well. Time has elapsed and threads do not live forever.
$queryResult = gwmi Win32_Process -ComputerName $ComputerName -Credential $Credential | Where {$_.ProcessId -eq $Process.IDProcess}
$owner = if($queryResult){$queryResult.GetOwner().User}else{"Process DNE"}
#...
User = $owner
Not very pretty but accounts for the potential of a null return from the wmi query.

Formatting my date time

When I run the following code it outputs the information I want, but im having trouble with the final out put. It shows my UpTime as days,hours,minutes,seconds. I would just like Days and hours. And I would like to move the Uptime to display last on my list. The out put right now is Uptime, Computer, LastBootupTime.
Function Get-UpTime
{ Param ([string[]]$servers)
Foreach ($s in $servers)
{
$os = Get-WmiObject -class win32_OperatingSystem -cn $s -ErrorAction SilentlyContinue
New-Object psobject -Property #{computer=$s;
LastBootUpTime = [Management.ManagementDateTimeConverter]::ToDateTime( (Get-WmiObject - Class Win32_OperatingSystem -Computer $s | Select -Exp LastBootUpTime) )
UpTime = (Get-Date) – [System.Management.ManagementDateTimeconverter]::ToDateTime((Get-WmiObject - Class Win32_OperatingSystem -Computer $s | Select -Exp LastBootUpTime))
}
}}
Get-UpTime -servers $servers|
ConvertTo-Html -As Table -body "
<h1>Server Uptime Report</h1>
The following report was run on $(get-date)" >> $path
Invoke-Item $path
Next code snippet gives desired output: shows UpTime as days.hours:minutes last on the list; moreover, displays all tested servers (shows N/A in place of LastBootUpTime and UpTime if an RPC server is unavailable).
Function Get-UpTime
{ Param ([string[]]$servers)
Foreach ($s in $servers) {
Try
{ $os = Get-WmiObject -class win32_OperatingSystem -cn $s
$ST = [System.Management.ManagementDateTimeconverter]::
ToDateTime(($os | Select -Exp LastBootUpTime))
$UT = (Get-Date) – $ST # Days.Hours:Minutes:Seconds.MillisecondsFraction
# e.g. 114.12:32:42.6025504
New-Object psobject -Property #{
Computer = $s
#UpTime = $UT.ToString().Substring(0, $UT.ToString().Length - 11)
UpTime = '' + $UT.Days + '.' + $UT.Hours + ':' + $UT.Minutes
LastBootUpTime = $ST
}
}
Catch
{ New-Object psobject -Property #{
Computer = $s
UpTime = "N/A"
LastBootUpTime = "N/A"
}
}
}
}
Get-UpTime -servers $servers |
ConvertTo-Html -As Table -Property Computer, LastBootUpTime, UpTime -body "
<h1>Server Uptime Report</h1>
The following report was run on $(get-date)" >> $path
Invoke-Item $path

PowerShell add objects to body of email

I want to be able to collect information from this script and put it in the body of an email. For the most part it seems to work except I want serialnumber, osversion, model, loggedin on different lines in the body of the email.
[CmdletBinding()]
param (
$computername = (Read-Host "Enter Computer Name"),
$subject = 'Serial Numbers',
$to = 'test#test.com',
$bcc = 'test#test.com',
$body = $Collection,
#$body = (Get-Content -path "c:\powershell\serialnumber\SerialNumber.csv"),
#$mail = "mailto:$to&subject=$subject&body=$body",
[string]$ErrorLog = 'c:\powershell\useful\errorlog\retry.txt',
[switch]$LogErrors
)
[Array]$Collection = foreach($computer in $computername)
{
$os = Get-WmiObject `
Win32_OperatingSystem -computer $computer
$bios = Get-WmiObject `
Win32_BIOS -computer $computer
$model = Get-WmiObject `
Win32_ComputerSystem -computer $computer
$Monitor = Get-WmiObject -computer $computer WmiMonitorID -Namespace root\wmi |
ForEach-Object {($_.UserFriendlyName -notmatch 0 |
foreach {[char]$_}) -join ""; ($_.SerialNumberID -notmatch 0 |
foreach {[char]$_}) -join ""}
$body = New-Object -TypeName PSObject -Property #{
Computername = $computer;
LoggedIn = $Model.username;
OSVersion = $os.Caption;
SerialNumber = $bios.SerialNumber;
Model = $Model.Model;
Monitor = $Monitor;
}
#$obj | convertTo-csv | out-string
#$obj = $body
#$Collection = [string]$body
#[String]$Collection
[string]$body
Start-Process -FilePath "mailto:$to&bcc=$bcc&subject=$subject&body=$body"
}
$Collection
You can't use an object directly as the body of an email, it must be a [string]. Try this:
$body = New-Object -TypeName PSObject -Property #{
Computername = $computer;
LoggedIn = $Model.username;
OSVersion = $os.Caption;
SerialNumber = $bios.SerialNumber;
Model = $Model.Model;
Monitor = $Monitor;
} | format-list | out-string