Disk Storage alert email - Powershell - windows

Script originally created by GhillieHammer
I've added additional features to work for me.
# $drives = #("D","E","F");
$drives = $null
# The minimum disk size to check for raising the warning
$minSize = 20GB
$MathminSize = [math]::round($minSize/1GB,2)
$minString = ($MathminSize).ToString()
$minString = $minString + "GB"
$email_to_addressArray = #("");
# $email_to_addressArray = #("toSingle#company.com")
#Set a couple needed variables
$SendIt = $Null
$ThisHost = $env:computername
$IPAddy = Invoke-RestMethod -Uri ('http://ipinfo.io/'+(Invoke-WebRequest -uri "http://ifconfig.me/ip").Content) | Select ip
#Check for $Drives query mode and make an array of them
if ($drives -eq $null -Or $drives -lt 1) {
$localVolumes = Get-WmiObject win32_volume;
$drives = #();
foreach ($vol in $localVolumes) {
if ($vol.DriveType -eq 3 -And $vol.DriveLetter -ne $null ) {
$drives += $vol.DriveLetter[0];
}
}
}
# Enumerate through the array of drives
# check to see if any are below minimum
# if so, set a flag saying as much and then
# add them and their information to the array we'll be adding to the email
foreach ($d in $drives) {
$disk = Get-PSDrive $d;
$MathDiskFree = [math]::round($disk.Free/1GB,2)
$MathDiskUsed = [math]::round($disk.Used/1GB,2)
$MathDisktotal = [math]::round($MathDiskFree + $MathDiskUsed)
$MathDiskPerc = ($MathDiskFree / $MathDiskUsed).tostring("P")
if ($disk.Free -lt $minSize) {
$SendIt = 1
$space += ("Free space on drive " + $d + " = " + $MathDiskFree + "GB. This is equal to only " + $MathDiskPerc + " of the " + $MathDisktotal + "GB total space available on this drive.<br>")
}
}
# Check the flag to see if it's set, meaning there's at least one drive below minimum free space, and if so, fire off the email(s)
If ($SendIt -eq 1) {
# Enumerate through the array of email addresses and fire off a formatted email to each
foreach ($toAddress in $email_to_addressArray) {
$User = "diskspace#"
$File = (Get-Content C:\Temp\pw.txt | ConvertTo-SecureString)
$MyCredential = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList $User, $File
$To = $toAddress
$from = ""
$EmailSubject = "WARNING: one or more disk on $ThisHost low on space"
$smtp = "auth.smtp.1and1.co.uk"
$DefaultMessage="
<p>Dear colleague,</p>
<p>There is a hard drive space issue on $ThisHost IPv4: $IPAddy </p>
<p>$space<br></p>
<p>This message only fires off if one or more disks have less than $minString GB of free space left.</p>
<p>Sincerely,<br>
Robot Monitor.<br><br>
</p>"
$MailMessage = #{
To = $To
From = $from
# BCC = $Bcc
Subject = $EmailSubject
Body = $DefaultMessage
priority = "High"
Smtpserver = $smtp
Credential = $MyCredential
ErrorAction = "SilentlyContinue"
}
Send-MailMessage #MailMessage -bodyashtml
}
}
The script works perfectly but can't seem to get it stop generating the following information
Can't seem to find or understand how it's generating additional lines of text. Any insight would be greatly appreciated!

Try using a more simplified code like the one below. I use this with a scheduled task on the machines I need it on. It only sends on email if disk space gets below threshold. You could modify it to work with the other drives you have. It's not really clear how you are getting multiple line output but modifying it would help resolve your problem and simplify your code.
$minGbThreshold = 10;
$computers = $env:COMPUTERNAME;
$smtpAddress = "smtp.yourdomain.com";
$toAddress = "anyone#gmail.com";
$fromAddress = "someone#gmail.com";
foreach($computer in $computers)
{
$disks = Get-WmiObject -ComputerName $computer -Class Win32_LogicalDisk -Filter "DriveType = 3";
$computer = $computer.toupper();
$deviceID = $disk.DeviceID;
foreach($disk in $disks)
{
$freeSpaceGB = [Math]::Round([float]$disk.FreeSpace / 1073741824, 2);
if($freeSpaceGB -lt $minGbThreshold)
{
$smtp = New-Object Net.Mail.SmtpClient($smtpAddress)
$msg = New-Object Net.Mail.MailMessage
$msg.To.Add($toAddress)
$msg.From = $fromAddress
$msg.Subject = “Diskspace below threshold ” + $computer + "\" + $disk.DeviceId
$msg.Body = $computer + "\" + $disk.DeviceId + " " + $freeSpaceGB + "GB Remaining";
$smtp.Send($msg)
}
}
}

Related

Can PowerShell be an ActiveScriptEventConsumer?

I need to continuously watch a particular "drop" folder for (tiny) pdfs and auto-print and delete them. The printing itself is being handled by SumatraPDF (because I don't think there is a built-in, Windows method).
Creating a temporary WMI event monitor in PowerShell is pretty straightforward:
$WQLquery = "SELECT * FROM __InstanceCreationEvent WITHIN 5 " +
"WHERE TargetInstance ISA 'CIM_DataFile' " +
"AND TargetInstance.Extension = 'pdf' " +
"AND TargetInstance.Drive = '$($Drive):' " +
"AND TargetInstance.Path = '$($Folder.replace('\','\\'))'"
$Action = {
$TimedOut = $null
$PrintProcess = Start-Process -FilePath $Sumatra -ArgumentList "-Print-To $Printer `"$($EventArgs.NewEvent.TargetInstance.Name)`"" -PassThru
$PrintProcess | Wait-Process -Timeout 10 -ErrorAction SilentlyContinue -ErrorVariable TimedOut
if ($TimedOut) {
"Printing $($EventArgs.NewEvent.TargetInstance.Name) timed out." | Out-File $ErrorLogPath -Append
$PrintProcess.kill
} elseif ($PrintProcess.ExitCode -ne 0) {
"Error for $($EventArgs.NewEvent.TargetInstance.Name) : $($PrintProcess.ExitCode)" | Out-File $ErrorLogPath -Append
} else {
Remove-Item $EventArgs.NewEvent.TargetInstance.Name -Force
}
}
Register-CimIndicationEvent -Query $wqlquery -SourceIdentifier 'FileCreated' -Action $Action
When it comes to creating a permanent event subscription, I can replace the Action and Register-CimIndicationEvent with:
# This creates a permanent event monitor that lasts through a reboot
# Step One: Create a Filter object
$FilterArgs = #{
ClassName = '__EventFilter'
NameSpace = 'root\subscription'
Computername = $env:COMPUTERNAME
ErrorAction = 'Stop'
Property = #{
Name = 'PDFCreatedFilter'
EventNamespace = 'root\CIMV2'
QueryLanguage = 'WQL'
Query = $WQLQuery
}
}
$FilterInstance = New-CimInstance #FilterArgs
# Step Two: Create a Consumer object
$ConsumerArgs = #{
ClassName = 'ActiveScriptEventConsumer'
NameSpace = 'root\subscription'
Computername = $env:COMPUTERNAME
ErrorAction = 'Stop'
Property = #{
Name = 'FileCreatedConsumer'
EventNamespace = 'root\CIMV2'
ScriptingEngine = '' # <---- What goes here for PowerShell?
ScriptFileName = '' # <---- What goes here for PowerShell?
}
}
$ConsumerInstance = New-CimInstance #ConsumerArgs
# Step Three: Create a Binding object between the Filter and Consumer
$BindingArgs = #{
ClassName = '__FilterToConsumerBinding'
NameSpace = 'root\subscription'
Computername = $env:COMPUTERNAME
ErrorAction = 'Stop'
Property = #{
Filter = [Ref]$FilterArgs
Consumer = [Ref]$ConsumerArgs
}
}
$BindingInstance = New-CimInstance #BindingArgs
However, and you can see, I am unsure about the ActiveScriptEventConsumer. Microsoft's documentation says it can be "in an arbitrary scripting language", but every example I can find uses VBScript only. I'd really like to use PowerShell.
Is PowerShell a valid Scripting Engine?
If yes, can someone provide the syntax for the ScriptingEngine and ScriptText properties to get it to work?
If no, am I better off hacking together some noob VBScript (yuck!) or using an CommandLineEventConsumer to start a PowerShell script (that I then have to watch/protect)?
Thanks!

Execute PowerShell GUI locally but send output over the network

I am using the excellent Chrome-Kiosk script from https://github.com/alex-tomin/Tomin.Tools.KioskMode and it works great. I have modified it for our display boards at work and we use it flawlessly with 11 screens.
I wanted to modify it to make a single screen launcher, so I can open a small GUI box, enter the URL to display, and then the screen number that it should display on. I have created a small script and it works perfectly on my local machine. What I want to do is open the GUI on my screen and then send the two variables over the network to the display PC on the network. I had hoped that I would be able to do this with remote execution as found here: https://www.howtogeek.com/117192/how-to-run-powershell-commands-on-remote-computers/, but no luck.
Here is the GUI code:
function button ($title,$mailbx, $WF, $TF) {
[void][System.Reflection.Assembly]::LoadWithPartialName( "System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName( "Microsoft.VisualBasic")
$form = New-Object "System.Windows.Forms.Form";
$form.Width = 500;
$form.Height = 150;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
$textLabel1 = New-Object "System.Windows.Forms.Label";
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $mailbx;
$textLabel2 = New-Object "System.Windows.Forms.Label";
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $WF;
$textBox1 = New-Object "System.Windows.Forms.TextBox";
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;
$textBox2 = New-Object "System.Windows.Forms.TextBox";
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;
$defaultValue = ""
$textBox1.Text = $defaultValue;
$textBox2.Text = $defaultValue;
$button = New-Object "System.Windows.Forms.Button";
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = "Ok";
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
$form.Close();
};
$button.Add_Click($eventHandler) ;
# Add controls to all the above objects defined
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textLabel3);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$form.Controls.Add($textBox3);
$ret = $form.ShowDialog();
return $textBox1.Text, $textBox2.Text#, $textBox3.Text
}
$return= button "Monitoring Screen Selector" "Enter URL" "Enter Screen # from 1 to 11" #"Target Folder"
$return[0]
$return[1]
The first part of the script is the GUI, it passes $return[0] and $return[1] into the second part of the script which is below:
$chromePath = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$chromeArguments = '--new-window'
# if Window not moved (especially on machine start) - try increasing the delay.
$ChromeStartDelay = 3
Set-Location $PSScriptRoot
. .\HelperFunctions.ps1
# Kill all running instances
# &taskkill /im chrome* /F
Chrome-Kiosk $return[0] -MonitorNum $return[1]
So the GUI should open on the local PC, then send $return[0] and $return[1] to the computer with all of the displays plugged into it so that the second part of the script can receive those two inputs from the GUI and then activate the screen and URL.
The idea as that during an incident or event that isn't covered by our normal screens, we can throw a web page up there until it is resolved, then close it manually afterwards (unless somebody knows how to catch the PID of a specific Chrome instance, which I very much doubt, so that it can be terminated somehow)
Any hints on how I could do this?
Ok, so here is what I have done to make this work. I have created server end scripts that check a folder for 2 specific files, and once it sees them it sends that data to the main script that is expecting a monitor number and a URL. Hope somebody finds this useful.
You will need to go and download the original Chrome-Kiosk from https://alextomin.wordpress.com/2015/04/10/kiosk-mode-in-windows-chrome-on-multiple-displays/
SERVER SCRIPTS
I have put these into a folder called c:\scripts\ICVT
looper.PS1 - This will run constantly
Set-Location -Path C:\scripts\ICVT
while ($true) {
.\file_checker.ps1;
}
file_checker.PS1 - This is the script that looper runs. file_checker scans the folder for web.txt and mon.txt . Both must be present for the rest of the script to execute.
#Checks folder for web.txt and mon.txt . Both must be present for the rest of the
script to execute
Set-Location -Path C:\scripts\ICVT
$a = Test-Path web.txt
$b = Test-Path mon.txt
IF (($a -and $b -eq $True)) {
.\launcher.ps1
}
else {
Write-Host "Scanning For Files"
}
Start-Sleep -Seconds 5
launcher.PS1 - This is just a modified version of the original script
$chromePath = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$chromeArguments = '--new-window'
$web = (Get-Content -Path web.txt)
$mon = (Get-Content -Path mon.txt)
# if Window not moved (especially on machine start) - try increasing the delay.
$ChromeStartDelay = 5
Set-Location $PSScriptRoot
. .\HelperFunctions.ps1
Chrome-Kiosk $web -MonitorNum $mon
#Delete parameters after use
Start-Sleep -Seconds 5
del web.txt
del mon.txt
CLIENT SIDE
menu.PS1 - If you are wanting to push a specific screen to the display computer over the network then setup Network Path, but if you are connected to the local PC then Local Path is the one to setup. As long as looper is able to see the folder then it will work. There is a little logic in the menu so that the script wont execute if you close the window without putting in any details. (if you run launcher with no parameters it still executes chrome and sends it to a screen that is out of the array normally number 2 for some reason)
#################Local Path###################
Set-Location -Path c:\scripts\ICVT
#################Network Path#################
#Set-Location -Path \\somecomputer\c$\scripts\ICVT
function button ($title,$mailbx, $WF, $TF) {
###################Load Assembly for creating form & button######
[void][System.Reflection.Assembly]::LoadWithPartialName( “System.Windows.Forms”)
[void][System.Reflection.Assembly]::LoadWithPartialName( “Microsoft.VisualBasic”)
#####Define the form size & placement
$form = New-Object “System.Windows.Forms.Form”;
$form.Width = 500;
$form.Height = 150;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;
##############Define text label1
$textLabel1 = New-Object “System.Windows.Forms.Label”;
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $mailbx;
##############Define text label2
$textLabel2 = New-Object “System.Windows.Forms.Label”;
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $WF;
##############Define text label3
#$textLabel3 = New-Object “System.Windows.Forms.Label”;
#$textLabel3.Left = 25;
#$textLabel3.Top = 85;
#$textLabel3.Text = $TF;
############Define text box1 for input
$textBox1 = New-Object “System.Windows.Forms.TextBox”;
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;
############Define text box2 for input
$textBox2 = New-Object “System.Windows.Forms.TextBox”;
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;
############Define text box3 for input
#$textBox3 = New-Object “System.Windows.Forms.TextBox”;
#$textBox3.Left = 150;
#$textBox3.Top = 90;
#$textBox3.width = 200;
#############Define default values for the input boxes
$defaultValue = “”
$textBox1.Text = $defaultValue;
$textBox2.Text = $defaultValue;
#$textBox3.Text = $defaultValue;
#############define OK button
$button = New-Object “System.Windows.Forms.Button”;
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = “Ok”;
############# This is when you have to close the form after getting values
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
#$textBox3.Text;
$form.Close();};
$button.Add_Click($eventHandler) ;
#############Add controls to all the above objects defined
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textLabel3);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$form.Controls.Add($textBox3);
$ret = $form.ShowDialog();
#################return values
return $textBox1.Text, $textBox2.Text#, $textBox3.Text
}
$return= button “Monitoring Screen Selector” “Enter URL” “Enter Screen # from 1 to 11” #“Target Folder”
if ($return[0] -ne "") {
$return[0] > web.txt
}
if ($return[0] -eq "") {
exit
}
if ($return[1] -ne "") {
$return[1] > mon.txt
}
if ($return[0] -eq "") {
exit
}

How to do a "Clean Boot" with PowerShell?

Ideally, I'd like to write the current status of any apps/svcs that will be changed, so the system could be restored programmatically as well. This is why I chose a CSV file to hold that data.
Here is how far I am in documenting which of each needs to be disabled (and later re-enabled at next boot):
(PowerShell 5 for now)
$svcsRunning = #()
$svcResults = #()
$svcsRunning = Get-WmiObject Win32_Service -Filter "State = 'Running'"
ForEach ($s in $svcsRunning) {
$svcDetails = #{
Date = get-date
ExitCode = $s.ExitCode
Name = $s.Name
ProcessId = $s.ProcessId
StartMode = $s.StartMode
State = $s.State
Status = $s.Status
}
# for iterations
$svcResults += New-Object PSObject -Property $svcDetails
}
# when done, export
$svcResults | export-csv -Path c:\users\yumi\startupServices.csv -NoTypeInformation
$appsRunning = #()
$appResults = #()
$appsRunning = Get-WmiObject Win32_StartupCommand
ForEach ($a in $appsRunning) {
$appDetails = #{
Date = get-date
Caption = $a.Caption
ClassPath = $a.ClassPath
Command = $a.Command
Container = $a.Container
Description = $a.Description
Location = $a.Location
Name = $a.Name
Path = $a.Path
Site = $a.Site
User = $a.User
UserSID = $a.UserSID
}
# for iterations
$appResults += New-Object PSObject -Property $appDetails
}
# when done, export
$appResults | export-csv -Path c:\users\yumi\startupApplications.csv -NoTypeInformation
I'm not sure if the above can show all apps/svcs that need to be disabled for a clean boot.

Background job to refresh data on a form while script is running?

Hope some brave will help me !
I’m working from a few days now on a Powershell tool that display a dashboard on the screen corner to give some network “real-time” diagnostics of the computer. This dashboard can be minimized in the notification area.
First, I created a function that get the diagnostics and display the status on the form. I tried to refresh data with a timer (See: Real Time Data With Powershell GUI).
The problem was that my function took too much time to be executed and it froze the interface. So buttons on the form were no more usable… (See: Mouse event don't work after first timer Tick ).
Now, I try to use background jobs. My idea is to launch a job in a loop mode and get his status on a timer interval.
First, the Job couldn’t call a function declared in my main script (line 361). I know that I can use global and/or don’t use a function but write the code directly on the ScriptBlock but… even with this, I don’t know how to let the job turning in a loop and get his status by interval (line 368)…
You can see that I’m not really experimented in PowerShell and GUI… It’s been a while since I have developed and it was in another language…
Thank you in advance for your kind help
If you want to run it, I’ve uploaded the images folder here:
https://omerta.is/2A1L
Here is my code below
####################################### FUNCTIONS ########################################
#Get diagnostics status
function GetStatus
{
#Create a return table
[hashtable]$Return = #{}
###Test LAN adapter###
$EthernetAdapter = (Get-WMIObject Win32_NetworkAdapter | Select Name, NetConnectionStatus | Where Name -like "*Gigabit*")
If ($EthernetAdapter -ne $null)
{
If ($EthernetAdapter.NetConnectionStatus -eq 2)
{$Return.LANOnStatus = $True}
else
{$Return.LANOnStatus = $False}
}
else
{
$Return.LANOnStatus = $False
}
###Test Wi-Fi adapter###
$WiFiAdapter = (Get-WMIObject Win32_NetworkAdapter | Select Name, NetConnectionStatus | Where Name -like "*Wireless*")
If ($WiFiAdapter -ne $null)
{
If ($WiFiAdapter.NetConnectionStatus -eq 2)
{$Return.WiFiOnStatus = $True}
else
{$Return.WiFiOnStatus = $False}
}
else
{
$Return.WiFiOnStatus = $False
}
###Test Network (Default Gateway Reachable)###
$DefaultGateway = (Get-WmiObject -Class Win32_IP4RouteTable | where { $_.destination -eq '0.0.0.0' -and $_.mask -eq '0.0.0.0'} | Sort-Object metric1 | select nexthop, metric1, interfaceindex).nexthop
If ($DefaultGateway -ne $null)
{
If (Test-Connection -ComputerName $DefaultGateway -Count 1 -TTL 4 -Quiet)
{$Return.NetworkStatus = $True}
else
{$Return.NetworkStatus = $False}
}
else
{
$Return.NetworkStatus = $False
}
#Test Internet connection
$GoogleWebSite = "www.google.com"
Try
{$Return.GoogleStatus = Test-Connection -ComputerName $GoogleWebSite -Count 1 -TTL 64 -Quiet}
Catch
{$Return.GoogleStatus = $False}
###DontReplyToo###
$DontReplyTooWebSite = "mdm.pictet.com"
Try
{$Return.DontReplyTooStatus = Test-Connection -ComputerName $DontReplyTooWebSite -Count 1 -TTL 64 -Quiet}
Catch
{$Return.DontReplyTooStatus = $False}
###DONT REPLY###
$DontReplyWebsite = "www.idontreply.com"
Try
{$Return.DontReplyStatus = Test-Connection -ComputerName $DontReplyWebsite -Count 1 -TTL 64 -Quiet}
Catch
{$Return.DontReplyStatus = $False}
#Return the table
Return $Return
}
########################################### EVENTS ###########################################
#Manual Synchronization
function ManualSync
{
Sync-Content
}
#Open Browser from dashboard
function Open-Browser
{
&($BrowserExe)
}
#Open a help message on click on help icon
function Open-Help($HelpText)
{
[System.Windows.Forms.MessageBox]::Show($HelpText,"Hotlines",0,32)
}
#Close Dashboard form on click on minimize icon
function Minimize-Dashboard
{
#Close Dashboard on click minimize
$Form.WindowState = "Minimize"
$Form.Visible = $False
$objNotifyIcon.Visible = $True
#!!!!! TO CHECK !!!!!
Wait-Event -Timeout 15 -SourceIdentifier NotifyIcon_Event
Remove-Event -SourceIdentifier NotifyIcon_Event
}
#Open Dashboard form on double click on agent icon in notification area
function OpenDashboard
{
#Open Dashboard on double-click
$Form.Visible = $True
$Form.WindowState = "Normal"
}
######################################### ASSEMBLIES #########################################
[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Timers")
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
######################################### VAR & CONST ########################################
#Screen placement
$Screen = [system.windows.forms.screen]::PrimaryScreen
$FormPos = $Screen.Bounds.Width - 160
#Dashboard Timer
$Timer = New-Object 'System.Windows.Forms.Timer'
$Timer.Interval = 3000
#Home directory of the program
$HomeDir = Split-Path $script:MyInvocation.MyCommand.Path
#Dashboard Images
$Background = [system.drawing.image]::FromFile("$HomeDir\img\interface.png")
$GreenLight = [system.drawing.image]::FromFile("$HomeDir\img\greenlight.png")
$RedLight = [system.drawing.image]::FromFile("$HomeDir\img\redlight.png")
$SyncImg = [system.drawing.image]::FromFile("$HomeDir\img\sync.png")
$BrowserImg = [system.drawing.image]::FromFile("$HomeDir\img\browser.png")
$HelpImg = [system.drawing.image]::FromFile("$HomeDir\img\help.png")
$MinimizeImg = [system.drawing.image]::FromFile("$HomeDir\img\minimize.png")
#Agent Images
$IconStd = New-Object system.drawing.icon ("$HomeDir\img\icon.ico")
$IconFocus = New-Object system.drawing.icon ("$HomeDir\img\icon_focus.ico")
#Log files
$LogLastSync = ("$HomeDir\Logs\LastSync.log")
$LogSyncAgent = ("$HomeDir\Logs\SyncAgentError.log")
#Browser Module
$BrowserExe = "$HomeDir\bin\Browser.exe"
#Free File Sync Module
$SyncAgentExe = "$HomeDir\bin\SyncAgent.exe"
$SyncJob = "$HomeDir\bin\SyncSettings.xml"
#Strings
$HelpText = "Hotline: 432423432423423"
########################################### FORMS ###########################################
#Dashboard Windows Forms
$Form = New-Object System.Windows.Forms.Form
$Font = New-Object System.Drawing.Font("Arial Unicode MS",10,[System.Drawing.FontStyle]::Bold)
$Lb_LANOn = New-Object System.Windows.Forms.Label
$Lb_WiFiOn = New-Object System.Windows.Forms.Label
$Lb_Network = New-Object System.Windows.Forms.Label
$Lb_Google = New-Object System.Windows.Forms.Label
$Lb_DontReplyToo = New-Object System.Windows.Forms.Label
$Lb_DontReply = New-Object System.Windows.Forms.Label
$Pb_LANOn = New-Object System.Windows.Forms.PictureBox
$Pb_WiFiOn = New-Object System.Windows.Forms.PictureBox
$Pb_Network = New-Object System.Windows.Forms.PictureBox
$Pb_Google = New-Object System.Windows.Forms.PictureBox
$Pb_DontReplyToo = New-Object System.Windows.Forms.PictureBox
$Pb_DontReply = New-Object System.Windows.Forms.PictureBox
$Pb_Sync = New-Object System.Windows.Forms.PictureBox
$Pb_Browser = New-Object System.Windows.Forms.PictureBox
$Pb_Help = New-Object System.Windows.Forms.PictureBox
$Pb_Minimize = New-Object System.Windows.Forms.PictureBox
#Agent Windows Form
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objNotifyIcon.Add_Click({OpenDashboard})
Register-ObjectEvent $objNotifyIcon Click NotifyIcon_Event
#Dashboard Interface Generation
$Form.Size = '150, 220'
$Form.FormBorderStyle = 'None'
$Form.Name = "Form"
$Form.StartPosition = 'Manual'
$Form.Location = $FormPos.ToString()+', 10'
$Form.Text = "Dashboard"
$Form.Font = $Font
$Form.BackgroundImage = $Background
$Form.BackgroundImageLayout = "None"
$Form.TransparencyKey = "White"
$Form.BackColor = "White"
#$Form.Opacity = '.85'
$Form.Controls.Add($Lb_LANOn)
$Lb_LANOn.Location = '10, 30'
$Lb_LANOn.Size = '75, 20'
$Lb_LANOn.Text = "LAN On"
$Lb_LANOn.ForeColor = 'Gray'
$Lb_LANOn.BackColor = "Transparent"
$Form.Controls.Add($Lb_WiFiOn)
$Lb_WiFiOn.Location = '10, 55'
$Lb_WiFiOn.Size = '75, 20'
$Lb_WiFiOn.Text = "Wi-Fi On"
$Lb_WiFiOn.ForeColor = 'Gray'
$Lb_WiFiOn.BackColor = "Transparent"
$Form.Controls.Add($Lb_Network)
$Lb_Network.Location = '10, 80'
$Lb_Network.Size = '75, 20'
$Lb_Network.Text = "Network"
$Lb_Network.ForeColor = 'Gray'
$Lb_Network.BackColor = "Transparent"
$Form.Controls.Add($Lb_Google)
$Lb_Google.Location = '10, 105'
$Lb_Google.Size = '75, 20'
$Lb_Google.Text = "Google"
$Lb_Google.ForeColor = 'Gray'
$Lb_Google.BackColor = "Transparent"
$Form.Controls.Add($Lb_DontReplyToo)
$Lb_DontReplyToo.Location = '10, 130'
$Lb_DontReplyToo.Size = '75, 20'
$Lb_DontReplyToo.Text = "DontReplyToo"
$Lb_DontReplyToo.ForeColor = 'Gray'
$Lb_DontReplyToo.BackColor = "Transparent"
$Form.Controls.Add($Lb_DontReply)
$Lb_DontReply.Location = '10, 155'
$Lb_DontReply.Size = '75, 20'
$Lb_DontReply.Text = "Dont Reply"
$Lb_DontReply.ForeColor = 'Gray'
$Lb_DontReply.BackColor = "Transparent"
$Form.Controls.Add($Pb_LANOn)
$Pb_LANOn.Location = '115, 33'
$Pb_LANOn.Size = '15, 15'
$Pb_LANOn.Image = $RedLight
$Pb_LANOn.BackColor = "Transparent"
$Form.Controls.Add($Pb_WiFiOn)
$Pb_WiFiOn.Location = '115, 58'
$Pb_WiFiOn.Size = '15, 15'
$Pb_WiFiOn.Image = $RedLight
$Pb_WiFiOn.BackColor = "Transparent"
$Form.Controls.Add($Pb_Network)
$Pb_Network.Location = '115, 83'
$Pb_Network.Size = '15, 15'
$Pb_Network.Image = $RedLight
$Pb_Network.BackColor = "Transparent"
$Form.Controls.Add($Pb_Google)
$Pb_Google.Location = '115, 108'
$Pb_Google.Size = '15, 15'
$Pb_Google.Image = $RedLight
$Pb_Google.BackColor = "Transparent"
$Form.Controls.Add($Pb_DontReplyToo)
$Pb_DontReplyToo.Location = '115, 133'
$Pb_DontReplyToo.Size = '15, 15'
$Pb_DontReplyToo.Image = $RedLight
$Pb_DontReplyToo.BackColor = "Transparent"
$Form.Controls.Add($Pb_DontReply)
$Pb_DontReply.Location = '115, 158'
$Pb_DontReply.Size = '15, 15'
$Pb_DontReply.Image = $RedLight
$Pb_DontReply.BackColor = "Transparent"
#Toolbar
$Form.Controls.Add($Pb_Sync)
$Pb_Sync.Location = '20, 195'
$Pb_Sync.Size = '20, 20'
$Pb_Sync.Image = $SyncImg
$Pb_Sync.BackColor = "Transparent"
$Pb_Sync.Add_Click({Sync-Content})
$Form.Controls.Add($Pb_Browser)
$Pb_Browser.Location = '51, 195'
$Pb_Browser.Size = '20, 20'
$Pb_Browser.Image = $BrowserImg
$Pb_Browser.BackColor = "Transparent"
$Pb_Browser.Add_Click({Open-Browser})
$Form.Controls.Add($Pb_Help)
$Pb_Help.Location = '82, 195'
$Pb_Help.Size = '20, 20'
$Pb_Help.Image = $HelpImg
$Pb_Help.BackColor = "Transparent"
$Pb_Help.Add_Click({Open-Help($HelpText)})
$Form.Controls.Add($Pb_Minimize)
$Pb_Minimize.Location = '113, 195'
$Pb_Minimize.Size = '20, 20'
$Pb_Minimize.Image = $MinimizeImg
$Pb_Minimize.BackColor = "Transparent"
$Pb_Minimize.Add_Click({Minimize-Dashboard})
########## MAIN
#Status Init
#$Status = GetStatus
#Create the Status Background Job
Start-Job -Name "jobGetStatus" -ScriptBlock {$Status = GetStatus} #function not recognized by Background Job
#Wait for job result
While (Get-Job -Name "jobGetStatus" | where { $_.State -eq "Running" })
{Start-Sleep 1}
#Get Status on Timer Tick
$Timer.Add_Tick({Get-Job -Name "jobGetStatus" | Receive-Job}) #job not updated
#Display new status on tick
$Timer.Add_Tick({
#LAN
If ($Status.LANOnStatus -eq $True)
{$Pb_LANOn.Image = $GreenLight}
else
{$Pb_LANOn.Image = $RedLight}
#WIFI
If ($Status.WiFiOnStatus -eq $True)
{$Pb_WiFiOn.Image = $GreenLight}
else
{$Pb_WiFiOn.Image = $RedLight}
#NETWORK
If ($Status.NetworkStatus -eq $True)
{$Pb_Network.Image = $GreenLight}
else
{$Pb_Network.Image = $RedLight}
#GOOGLE
If ($Status.GoogleStatus -eq $True)
{$Pb_Google.Image = $GreenLight}
else
{$Pb_Google.Image = $RedLight}
#DontReplyToo
If ($Status.DontReplyTooStatus -eq $True)
{$Pb_DontReplyToo.Image = $GreenLight}
else
{$Pb_DontReplyToo.Image = $RedLight}
#BING
If ($Status.DontReplyStatus -eq $True)
{$Pb_DontReply.Image = $GreenLight}
else
{$Pb_DontReply.Image = $RedLight}
})
#Start Timer
$Timer.Enabled = $True
#Agent Init
$objNotifyIcon.Icon = $IconStd
$objNotifyIcon.Text = "Dashboard"
$objNotifyIcon.Visible = $True
#Show Interface
$Form.ShowDialog()
############## END
Nice little utility.
I removed all references to the Job, and added the code from the job into the Tick itself:
$Timer.Add_Tick({
$Status = GetStatus
#LAN
If ($Status.LANOnStatus -eq $True)
{$Pb_LANOn.Image = $GreenLight}
else
{$Pb_LANOn.Image = $RedLight}
...
})
Since you mentioned the function took too much time, I also increased the Tick interval from 3 to 10 seconds. This seems to work fine on my PC.

64bit equivalent class for a wmi class "win32reg_addremoveprograms"?

My code below works perfectly on a 32-bit Windows machine, however it refuses to work on a 64-bit machine due to the 32-bit WMI class win32reg_addremoveprograms used in the code. Is there a 64-bit equivalent of this class?
$ServerFile = "D:\SharePoint\Powershell\AddRemovePrograms\Machines.txt"
$ServerList = Get-Content $ServerFile
$Excel = New-Object -Com Excel.Application
$Excel.displayalerts=$False
$Excel.visible = $True
$workbook = $Excel.Workbooks.Add()
$workbook.workSheets.item(2).delete()
$workbook.WorkSheets.item(2).delete()
$Sheet = $workbook.WorkSheets.Item(1)
$Sheet.Name= "Program List";
$intRow = 1
foreach ($NextServer in $ServerList)
{
$Sheet.Cells.Item($intRow,1) = “Computer Name”
$Sheet.Cells.Item($intRow,2) = $NextServer
$Sheet.Cells.Item($intRow,1).Interior.ColorIndex = 8
$Sheet.Cells.Item($intRow,1).Font.ColorIndex = 11
$Sheet.Cells.Item($intRow,1).Font.Bold = $True
$Sheet.Cells.Item($intRow,2).Interior.ColorIndex = 8
$Sheet.Cells.Item($intRow,2).Font.ColorIndex = 11
$Sheet.Cells.Item($intRow,2).Font.Bold = $True
$intRow = $intRow + 2
$Sheet.Cells.Item($intRow,1) = "Programs"
$Sheet.Cells.Item($intRow,1).Interior.ColorIndex = 12
$Sheet.Cells.Item($intRow,1).Font.ColorIndex = 8
$Sheet.Cells.Item($intRow,1).Font.Bold = $True
$intRow = $intRow + 1
$colItems =get-wmiobject -class "win32reg_addremoveprograms" -computername $NextServer | select-object -property DisplayName | sort-object DisplayName
foreach ($objItem in $colItems)
{
if ($objItem.DisplayName.IndexOf('Update') -eq -1 -and $objItem.DisplayName.IndexOf('2007 Microsoft Office') -eq -1)
{
$Sheet.Cells.Item($intRow,1) = $objItem.DisplayName
$intRow = $intRow + 1
}
}
$intRow = $intRow + 2
}
$workArea = $Sheet.UsedRange
$workArea.EntireColumn.AutoFit()
$workbook.SaveAs("D:\SharePoint\Powershell\AddRemovePrograms\Programs.xls")
$workbook.close()
$a = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workArea)
$a = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($Sheet)
$a = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($workbook)
$Excel.Quit()
$a = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($Excel)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Remove-Variable Excel, intRow ,workbook, Sheet,colItems, ServerFile, ServerList, a
Do you have the SCCM/SMS client installed on your 64-bit machine? Because the Win32Reg_AddRemovePrograms WMI class is added by the SCCM/SMS client, and therefore isn't available if you don't have it installed.
Note also that Win32Reg_AddRemovePrograms provide info on 32-bit installed applications only. To get info on 64-bit applications, use the Win32Reg_AddRemovePrograms64 class (available in SMS 2003 SP3 and later).

Resources