Collection of PS Functions for useful GUI elements

This time, I have started a project to create library of useful PowerShell functions to create UI elements like OkCancel box, FileBrowser, Ballon Notification and most lovely toast notifications. Would keep on adding the functions as the use cases come to mind. The aim is to keep it simple, readable and portable with least dependencies.

Let’s see how it goes. Any suggestions welcome


Add-Type AssemblyName System.Windows.Forms
Add-Type AssemblyName System.Windows.Forms.DataVisualization
function New-SplashFromImage{
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $true,mandatory=$true)][String]$imageFilePath
)
Add-Type AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles() # To enable system theme
$img = [System.Drawing.Image]::Fromfile($imageFilePath)
$SScreen = New-Object system.Windows.Forms.Form
$SScreen.Width = $img.Width
$SScreen.Height = $img.Height
$SScreen.TopMost = $true
$SScreen.BackgroundImage = $img
$SScreen.AllowTransparency = $true
$SScreen.TransparencyKey = $SScreen.BackColor
$SScreen.StartPosition = 1 # 0: Manual, 1: CenterScreen, 2: WindowsDefaultLocation, 3:WindowsDefaultBounds, 4:CenterParent
$SScreen.FormBorderStyle = 0 # 0: None, 1: FixedSingle, 2:Fixed3D, 3:FixedDialog, 4:Sizable, 5:FixedToolWindow, 6:SizableToolWindow
$SScreen.Show()
Start-Sleep Seconds 5
$SScreen.Close()
$SScreen.Dispose()
} #New-SplashFromImage -imageFilePath "C:\temp\light.png"
function New-OkCancelBox{
Param(
[Parameter(ValueFromPipeline = $true,mandatory=$true)][String]$title,
[Parameter(ValueFromPipeline = $true,mandatory=$true)][String]$message,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateSet('Hand','Question','Exclamation','Asterisk','Stop','Error','Warning','Information')][String]$icon = "Information"
)
Add-Type AssemblyName System.Windows.Forms
$result = [System.Windows.Forms.MessageBox]::Show($message, $title, [System.Windows.Forms.MessageBoxButtons]::YesNo,[System.Windows.Forms.MessageBoxIcon]::$icon)
return $result
} #$Output = New-OkCancelBox -title "Info Button" -message "Sample Info" -icon Stop
function New-FileDialog {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateScript({if( -Not($_ | Test-Path)){ throw "incorrect start Directory" } else {return $true}})][String]$startDirectory,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String[]]$filter = "All Files (*.*)|*.*"
)
Add-Type AssemblyName System.Windows.Forms
$fileBrowser = New-Object System.Windows.Forms.OpenFileDialog
if($startDirectory){
$fileBrowser.InitialDirectory = $startDirectory
}
else{
$fileBrowser.InitialDirectory = [Environment]::GetFolderPath('MyDocuments')
}
if ($filter -ne "All Files (*.*)|*.*"){
$fileBrowser.Filter = ($filter | Where-Object{$_ -match "\*\..*"} | ForEach-Object{"$_ files |$_"}) -join "|"
} else {
$fileBrowser.Filter = "All Files (*.*)|*.*"
}
[void]$fileBrowser.ShowDialog()
# Return the selected file name
return $fileBrowser.FileName
} #New-FileDialog -startDirectory "c:\" -filter @("*.txt","*.csv")
function New-BaloonNotification {
Param(
[Parameter(ValueFromPipeline = $true,mandatory=$true)][String]$title,
[Parameter(ValueFromPipeline = $true,mandatory=$true)][String]$message,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateSet('None','Info','Warning','Error')][String]$icon = "Info",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][scriptblock]$Script
)
Add-Type AssemblyName System.Windows.Forms
if ($null -eq $script:balloonToolTip){ $script:balloonToolTip = New-Object System.Windows.Forms.NotifyIcon }
$tip = New-Object System.Windows.Forms.NotifyIcon
$path = Get-Process id $pid | Select-Object ExpandProperty Path
$tip.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
$tip.BalloonTipIcon = $Icon
$tip.BalloonTipText = $message
$tip.BalloonTipTitle = $title
$tip.Visible = $true
register-objectevent $tip BalloonTipClicked BalloonClicked_event Action {$script.Invoke()} | Out-Null
$tip.ShowBalloonTip(50000) # Even if we set it for 1000 milliseconds, it usually follows OS minimum 10 seconds
Start-Sleep s 10
$tip.Dispose() # Important to dispose otherwise the icon stays in notifications till reboot
Get-EventSubscriber SourceIdentifier "BalloonClicked_event" ErrorAction SilentlyContinue | Unregister-Event # In case if the Event Subscription is not disposed
}
# You can pass a script that what to do when someone clicks on ballon notification
<# $script = {
Start-Process https://nitishkumar.net
}
New-BaloonNotification -title "The Notification from Nitish Kumar : " -message "Just trying out cool stuff" -script $Script #>
# Function to get password expiration date for the given samAccountName
function Get-PasswordExpiry {
Param(
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$samAcccountName = ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name -split "\\")[1]
)
# Step 1 – Find the max password age as per domain policy
$currentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() # Find current domain
$domain = [ADSI]"LDAP://$currentDomain" # find current domain LDAP coordinates
$maxPasswordAge = $domain.ConvertLargeIntegerToInt64($domain.maxPwdAge.Value)/(600000000 * 1440) # Max password age in days
# Step 2 – Find when the password was last set for the user
$searcher = New-Object DirectoryServices.DirectorySearcher # Create ADSI searcher
[adsisearcher]$searcher.Filter = "(&(samaccountname=$samAccountName))" # Create search query
$results = $searcher.FindOne() # Perform the search
$passwordLastSet = [datetime]::FromFileTimeUtc($results.Properties.pwdlastset) # Find when the password was last set
# Step 3 – Find when the password would be expired for the user
$passwordExpirationDate = $passwordLastSet.AddDays($maxPasswordAge)
Return $passwordExpirationDate
}
# Function to extract icon from the given file
function Get-Icon {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the path to the file",mandatory=$true)][ValidateScript({if( -Not($_ | Test-Path)){ throw "File doesn't exists" } else {return $true}})][String]$fileName,
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the icon file type, default is Png",mandatory=$false)][ValidateScript({$_ -in (([System.Drawing.Imaging.ImageFormat] | get-member Static MemberType Properties)).Name})]$iconFormat = "Png",
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the folder to save the file",mandatory=$false)][ValidateScript({if( -Not($_ | Test-Path )){ throw "Folder doesn't exists" } else {return $true}})][string]$savePath=".",
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the icon file name",mandatory=$false)][ValidateNotNullOrEmpty()][String]$iconFileName = "icon",
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the icon index in dll",mandatory=$false)][ValidateRange(-2, [int]::MaxValue)][int]$dllIconIndex = -2,
[Parameter(ValueFromPipeline = $true,HelpMessage = "Specify the icon size in dll (small or large)",mandatory=$false)][ValidateSet('small','large')][string]$dllIconSize,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][switch]$asBase64
)
Add-Type AssemblyName System.Drawing ErrorAction Stop
if ([System.IO.Path]::GetExtension($filename) -ne ".dll"){
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($fileName)
if($asBase64){
$ms = New-Object System.IO.MemoryStream
$icon.save($ms)
$bytes = $ms.ToArray()
$base64 = [convert]::ToBase64String($Bytes)
$ms.Flush()
$ms.Dispose()
return $base64
} else {
$outPath = $savePath + "\" + $iconFileName + "." + $iconFormat
$icon.ToBitmap().Save($outPath,$iconFormat)
$finalPath = (get-item $outPath).FullName
#Write-Output "The icon file has been saved to $finalPath"
return $finalPath
}
} else {
# ref https://github.com/ReneNyffenegger/about-powershell/blob/master/examples/WinAPI/Shell32/Extract/Shell32_Extract.ps1
# using c# code to call function from shell32.dll
add-type typeDefinition '
using System;
using System.Runtime.InteropServices;
public class Shell32_Extract {
[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int ExtractIconEx(string lpszFile, int iconIndex, out IntPtr phiconLarge, out IntPtr phiconSmall, int nIcons );
}
';
$dllPath = $fileName
[System.IntPtr] $phiconSmall = 0
[System.IntPtr] $phiconLarge = 0
if($dllIconIndex -ge 0){
Write-Host "Now about single icon"
$dllIconIndex
$nofIconsExtracted = [Shell32_Extract]::ExtractIconEx($dllPath, $dllIconIndex, [ref] $phiconLarge, [ref] $phiconSmall, 1)
if ($nofIconsExtracted -ne 2) {
write-error "iconsExtracted = $nofIconsExtracted"
} else {
if ($dllIconSize -eq 'small'){
$bmpSmall = ([System.Drawing.Icon]::FromHandle($phiconSmall))
if($asBase64){
$ms = New-Object System.IO.MemoryStream
$bmpSmall.save($ms)
$bytes = $ms.ToArray()
$base64 = [convert]::ToBase64String($Bytes)
$ms.Flush()
$ms.Dispose()
return $base64
} else {
$outPath = $savePath + "\" + $iconFileName + "." + $iconFormat
$bmpSmall.ToBitmap().Save($outPath,$iconFormat)
$finalPath = (get-item $outPath).FullName
return $finalPath
}
} else {
$bmpLarge = ([System.Drawing.Icon]::FromHandle($phiconLarge))
if($asBase64){
$ms = New-Object System.IO.MemoryStream
$bmpLarge.save($ms)
$bytes = $ms.ToArray()
$base64 = [convert]::ToBase64String($Bytes)
$base64
$ms.Flush()
$ms.Dispose()
return $base64
} else {
$outPath = $savePath + "\" + $iconFileName + "." + $iconFormat
$bmpLarge.ToBitmap().Save($outPath,$iconFormat)
$finalPath = (get-item $outPath).FullName
return $finalPath
}
}
}
}
else {
$nofImages = [Shell32_Extract]::ExtractIconEx($dllPath, -1, [ref] $phiconLarge, [ref] $phiconSmall, 0)
foreach ($iconIndex in 0 .. ($nofImages1)) {
$nofIconsExtracted = [Shell32_Extract]::ExtractIconEx($dllPath, $iconIndex, [ref] $phiconLarge, [ref] $phiconSmall, 1)
if ($nofIconsExtracted -ne 2) { write-error "iconsExtracted = $nofIconsExtracted" }
$small = [System.Drawing.Icon]::FromHandle($phiconSmall)
$large = [System.Drawing.Icon]::FromHandle($phiconLarge)
$bmpSmall = $small.ToBitmap()
$bmpLarge = $large.ToBitmap()
$iconIndex_0 = '{0,3:000}' -f $iconIndex
$bmpSmall.Save("$($savePath)\$($iconFileName)_small-$($iconIndex_0).$($iconFormat)", [System.Drawing.Imaging.ImageFormat]::$iconFormat)
$bmpLarge.Save("$($savePath)\$($iconFileName)_large-$($iconIndex_0).$($iconFormat)", [System.Drawing.Imaging.ImageFormat]::$iconFormat)
}
}
}
}
#Get-Icon -fileName "$env:SystemRoot\System32\imageres.dll" -iconFormat "jpeg" -iconFileName "supericon" -savePath "C:\temp" -dllIconIndex 191 -dllIconSize "small" # -asBase64
# Function to generate Windows 10 Toast notification
function New-ToastNotification {
[cmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$title = "Test Title",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$message = "Test Message",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$Sender = "IT Team",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateSet('long','short')][String]$duration = "long",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$logo = $null,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$heroImage = $null,
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$extraImage = $null,
[Parameter(ValueFromPipeline = $true,mandatory=$false, ParameterSetName = "Actions",
HelpMessage = "Need to provide content, argument, protocol and optionally imgeuri")][string[]]$action1 = ("Nitish Kumar's Blog","http://nitishkumar.net","protocol","C:\temp\1616.png"),
[Parameter(ValueFromPipelineByPropertyName = $true,mandatory=$false, ParameterSetName = "Actions",
HelpMessage = "Need to provide content, argument, protocol and optionally imgeuri")][string[]]$action2,
[Parameter(ValueFromPipelineByPropertyName = $true,mandatory=$false, ParameterSetName = "Actions",
HelpMessage = "Need to provide content, argument, protocol and optionally imgeuri")][string[]]$action3
)
if ((Get-CimInstance win32_operatingSystem).version -lt 10){
# If OS less than Windows 10 then generate balloon notification instead
New-BaloonNotification title $title message $message icon Warning
return
}
if ($PSEdition -eq "Core") {
Write-Host "Script is running in PowerShell version $($PSVersionTable.PSVersion.Major).x so need to manually load libraries"
Add-Type Path "C:\temp\lib\Microsoft.Windows.SDK.NET.dll"
} else {
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
}
$appId = ((Get-StartApps Name "Windows Powershell") | Select-Object First 1).AppId
#Enable push notifications for system and the notificaiton for the app
$pushNotificationsEnabled = (get-ItemProperty Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\PushNotifications" ).ToastEnabled
if (!($pushNotificationsEnabled)) {
Set-ItemProperty Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\PushNotifications" Name ToastEnabled Value 1 Force
Get-Service Name WpnUserService** | Restart-Service Force
}
$appNotificationEnabled = (Get-ItemProperty Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\$appId").Enabled
if (!($appNotificationEnabled)) {
Set-ItemProperty Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\$appId" Name Enabled Value 1 Force
Get-Service Name WpnUserService** | Restart-Service Force
}
if($action1){ $firstAction = "<action content=""$($action1[0])"" arguments=""$($action1[1])"" activationType=""$($action1[2])"" imageUri=""$($action1[3])"" />" }
if($action2){ $secondAction = "<action content=""$($action2[0])"" arguments=""$($action2[1])"" activationType=""$($action2[2])"" imageUri=""$($action2[3])"" />" }
if($action3){ $thirdAction = "<action content=""$($action3[0])"" arguments=""$($action3[1])"" activationType=""$($action3[2])"" imageUri=""$($action3[3])"" />" }
if($firstAction -OR $secondAction -OR $thirdAction) {
$actionStart = '<actions>'
$actionClose = '</actions>'
}
[xml]$toastXml = '<?xml version="1.0" encoding="utf-8"?><toast><visual><binding template="ToastGeneric">' +
'<text></text><text></text><text></text><image src="" /><image src="" /><image src="" /></binding></visual>' +
$actionStart + $firstAction + $secondAction + $thirdAction + $actionClose +
'</toast>'
# Below lines are to create Base64 string for logo in case you want to burn that in the script itself than supplying from outside
<# $File = "C:\temp\logo1.png"
$Image = [System.Drawing.Image]::FromFile($File)
$MemoryStream = New-Object System.IO.MemoryStream
$Image.Save($MemoryStream, $Image.RawFormat)
[System.Byte[]]$Bytes = $MemoryStream.ToArray()
$Base64Image = [System.Convert]::ToBase64String($Bytes)
$Image.Dispose()
$MemoryStream.Dispose() #>
$Base64Image = "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"
# Can use below code block in case if you got base64 value from any logo with above proecss, no need to unblock earlier code
if ($logo.Length -le 1 -AND $Base64Image){
$logo = "$($env:TEMP)\logoImage.png"
[byte[]]$Bytes = [convert]::FromBase64String($Base64Image)
[System.IO.File]::WriteAllBytes($logo,$Bytes)
}
$toastXml.GetElementsByTagName("text")[0].AppendChild($toastXml.CreateTextNode($title)) | Out-Null
$toastXml.GetElementsByTagName("text")[1].AppendChild($toastXml.CreateTextNode($message)) | Out-Null
$toastXml.GetElementsByTagName("text")[2].SetAttribute("placement","Attribution") | Out-Null
$toastXml.GetElementsByTagName("text")[2].AppendChild($toastXml.CreateTextNode("Notifiaiton by " + $Sender)) | Out-Null
if ($logo.Length -ge 1){
if ($logo -match "http*"){ $wc = New-Object System.Net.WebClient; $logoLocal = "$($env:TEMP)\logoImage.png"; $wc.DownloadFile($logo, $logoLocal)}
else { $logoLocal = $logo }
$toastXml.GetElementsByTagName("image")[0].SetAttribute("placement","appLogoOverride") | Out-Null
$toastXml.GetElementsByTagName("image")[0].SetAttribute("hint-crop","circle") | Out-Null
$toastXml.GetElementsByTagName("image")[0].SetAttribute("src",$logoLocal) | Out-Null
}
if ($heroImage.Length -ge 1){
if ($heroImage -match "http*"){ $wc = New-Object System.Net.WebClient; $heroLocal = "$($env:TEMP)\heroImage.png"; $wc.DownloadFile($heroImage, $heroLocal)}
else { $heroLocal = $heroImage }
$toastXml.GetElementsByTagName("image")[1].SetAttribute("placement","hero") | Out-Null
$toastXml.GetElementsByTagName("image")[1].SetAttribute("src",$heroLocal) | Out-Null
}
if ($extraImage.Length -ge 1){
if ($extraImage -match "http*"){ $wc = New-Object System.Net.WebClient; $extraImageLocal = "$($env:TEMP)\extraImage.png"; $wc.DownloadFile($extraImage, $extraImageLocal)}
else { $extraImageLocal = $extraImage }
$toastXml.GetElementsByTagName("image")[2].SetAttribute("src",$extraImageLocal) | Out-Null
}
$toastXml.toast.SetAttribute("duration",$duration)
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($toastXml.OuterXml)
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)
}
#Invoke-Command -ComputerName 192.168.1.22 -ScriptBlock ${function:New-ToastNotification} -Credential (Get-Credential)
#New-ToastNotification -title "This is custom notification" -message "One liner custom message. Some extra long, super long message " -logo "C:\temp\extra.png" -extraImage "C:\temp\extra.png" -heroImage "C:\temp\hero.png" -action1 ("Nitish Kumar Blog","https://nitishkumar.net&quot;,"protocol","C:\temp\1616.png") -action2 ("Ignore","dismiss","system","C:\temp\logo.png") -action3 ("Ignore-test","dismiss","system")
# Funtion to create a save dialog
function New-SaveDialog {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateScript({if( -Not($_ | Test-Path)){ throw "incorrect start Directory" } else {return $true}})][String]$startDirectory = [Environment]::GetFolderPath('MyDocuments'),
[Parameter(ValueFromPipeline = $true,mandatory=$false)][String]$filter = "*.png"
)
Add-Type AssemblyName System.Windows.Forms
$SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
if($startDirectory){ $SaveFileDialog.InitialDirectory = $startDirectory }
else{ $SaveFileDialog.InitialDirectory = [Environment]::GetFolderPath('MyDocuments') }
if ($filter -ne "*.png"){
$SaveFileDialog.Filter = ($filter | Where-Object{$_ -match "\*\..*"} | ForEach-Object{"$_ files |$_"}) -join "|"
} else {
$SaveFileDialog.Filter = ($filter | Where-Object{$_ -match "\*\..*"} | ForEach-Object{"$_ files |$_"}) -join "|"
#$SaveFileDialog.DefaultExt='png'
}
$show = $SaveFileDialog.ShowDialog()
If ($show -eq 'OK') {
$file = [pscustomobject]@{ FileName = $SaveFileDialog.FileName;Extension = $SaveFileDialog.FileName -replace '.*\.(.*)','$1'}
}
Return $file
}
# Function to create Chart
function New-Chart {
[cmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$xAxis,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$yAxis,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$width = 700,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$height = 400,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$left = 10,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$top = 10,
[Parameter(ValueFromPipeline = $true,mandatory=$false)]$title = "",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateScript({$_ -in ([System.Drawing.Color] | Get-Member Static MemberType Properties).Name})]$chartColor = "Transparent",
[Parameter(ValueFromPipeline = $true,mandatory=$false)][ValidateScript({$_ -in ([System.Windows.Forms.DataVisualization.Charting.SeriesChartType] | Get-Member Static MemberType Properties).Name})]$chartType,
[Parameter(ValueFromPipeline = $true,ParameterSetName = "extra",mandatory=$true)][bool]$generateImage,
[Parameter(ValueFromPipeline = $true,ParameterSetName = "extra",mandatory=$false)][string]$imgExt="png",
[Parameter(ValueFromPipeline = $true,ParameterSetName = "extra",mandatory=$false)][string]$saveImagePath="$env:TEMP\image.png"
)
Add-Type AssemblyName System.Windows.Forms
Add-Type AssemblyName System.Windows.Forms.DataVisualization
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Series = New-Object TypeName System.Windows.Forms.DataVisualization.Charting.Series
$ChartTypes = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]
$Series.ChartType = $ChartTypes::$chartType
$Chart.Series.Add($Series)
$Chart.ChartAreas.Add($ChartArea)
$Chart.Series['Series1'].Points.DataBindXY($xAxis, $yAxis)
$Chart.Width = $width
$Chart.Height = $height
$Chart.Left = $left
$Chart.Top = $top
$Chart.BackColor = [System.Drawing.Color]::$chartColor
$Chart.BorderColor = 'Black'
$Chart.BorderDashStyle = 'Solid'
$ChartTitle = New-Object System.Windows.Forms.DataVisualization.Charting.Title
$ChartTitle.Text = $title
$Font = New-Object System.Drawing.Font @('Microsoft Sans Serif','12', [System.Drawing.FontStyle]::Bold)
$ChartTitle.Font =$Font
$Chart.Titles.Add($ChartTitle)
$Legend = New-Object System.Windows.Forms.DataVisualization.Charting.Legend
$Legend.IsEquallySpacedItems = $True
$Legend.BorderColor = 'Black'
$Chart.Legends.Add($Legend)
$chart.Series["Series1"].LegendText = "#VALX (#VALY)"
$Chart.Series['Series1']['PieLineColor'] = 'Black'
$Chart.Series['Series1']['PieLabelStyle'] = 'Outside'
$Chart.Series['Series1'].Label = "#VALX (#VALY)"
$ChartArea.Area3DStyle.Enable3D=$True
$ChartArea.Area3DStyle.Inclination = 60
if($generateImage) {
$Chart.SaveImage($saveImagePath, $imgExt)
}
Return $Chart
}
#$Processes = Get-Process | Sort-Object WS -Descending | Select-Object -First 10
#$s = New-Chart -xAxis $Processes.Name -yAxis $Processes.WS -chartType "Pie" -generateImage $true

Advertisement

One stop Bash script to setup Prometheus, Grafana and Windows Exporter on CentOS Linux Machine

In the last post, we covered about the PowerShell script to install Prometheus, Grafana and Windows Exporter on a Windows Server, which was more of a fun project considering most organizations or people would prefer to have these installed over Linux servers (At least Prometheus and Grafana for sure) and not sure anyone would prefer the same in production considering cost, performance and other reasons. Windows nodes anyways can send data to such Linux Servers.

I do not have background in shell scripting so earlier, I did post step by step guide about how to setup the same over a Linux server but post the PowerShell script, I thought why not to replicate the same with a single Bash script as it would be lot fun to learn the language in the process. So here we go with the first version of script.

The script is not exactly similar to the windows one and as of now do not have removal and configuration related functions. Also I would need to see how it can work over remote nodes for scaling purposes but AS IS it does download the latest versions of the packages and then installs those.

Nitish Kumar
Continue reading “One stop Bash script to setup Prometheus, Grafana and Windows Exporter on CentOS Linux Machine”

One stop PowerShell script to setup Prometheus, Grafana and Windows Exporter on Windows Machine

After setting up Prometheus, Grafana over Linux host, I was approached for similar setup over Windows. That had separate set of challenges since Prometheus and Grafana projects, despite of requests in last couple of years, are not yet compatible with Microsoft Service Framework. Even official documentation proposes solution via nssm, which is another OSS project (in a way).

Though I would have created similar step by step doc for Windows, thought to do better this time and almost half day work brought us a PowerShell script, which can download almost all the latest packages (except nssm, which is not github hosted) and then proceed to configure them on the same machine. Functions to download and create services are created in a way that select set of package can be downloaded or installed as putting everything on the same server may not be good idea.

This is still not ready for production use as there might be random quirks but here we do with the complete script, which has an experiment Prometheus configuration function as well.

Continue reading “One stop PowerShell script to setup Prometheus, Grafana and Windows Exporter on Windows Machine”

Monitoring IT Infra with Prometheus and Grafana – Part 3

In last post, we created Dashboards for Linux nodes monitoring. Please note, Linux and Windows are not only things which can be monitored but would leave that part to figure you out in case if you have any use case.

Let’s talk about creating a Windows summary dashboard this time like below one. It’s for just one node but it would keep on scaling as more nodes added in Prometheus target config.

Continue reading “Monitoring IT Infra with Prometheus and Grafana – Part 3”

Monitoring IT Infra with Prometheus and Grafana – Part 2

In last post, we created a setup with Prometheus, Node Exporter and Grafana. In this post, we would proceed to create Dashboards, which is the real beauty of the setup.

With Monitoring Dashboards, I would recommend having two step Dashboard system means one summary dashboard which covers all the nodes at once and then different node names hyper-linked with a detailed dashboard specific to that node. In different scenarios, rather than node, it might be process or application or anything else whose performance you want to see in details but here we would focus on Linux or Windows Node overall metrics for simplicity.

Let’s talk about creating a Linux summary dashboard like below one. It’s for just one node but it would keep on scaling as more nodes added in Prometheus target config.

Continue reading “Monitoring IT Infra with Prometheus and Grafana – Part 2”

Monitoring IT Infra with Prometheus and Grafana – Part 1

Recently in pursuit of Open Source solution to monitor a stack of Jenkins and Team City nodes, I got chance to explore Prometheus and Grafana, which brought back all the fun, I used to have with CentOS and Open Source. That prompted me to write a step-by-step guide about setting up one.

Roughly 12 years back, I wrote a blog about Zabbix Monitoring when I was managing couple of data centres of a BPO in Delhi. In those days, I worked over a number of solutions over CentOS but later on in career,  I got more engaged in Windows side of world. Microsoft Azure, O365 and more so Windows PowerShell and lost touch with such Open Source solutions for monitoring. But what better to resume the touch than Prometheus and Grafana.

Windows Dashboard

Let’s go through introduction first, what is Prometheus? Some sci-fi movie title?

Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud to meet requirements like a multi-dimensional data model, operational simplicity, scalable data collection, and a powerful query language, all in a single tool. The project was open-source from the beginning and began to be used by Boxever and Docker users as well, despite not being explicitly announced. Prometheus was inspired by the monitoring tool Borgmon used at Google. In May 2016, the Cloud Native Computing Foundation accepted Prometheus as its second incubated project, after Kubernetes. The blog post announcing this stated that the tool was in use at many companies including DigitalOcean, Ericsson, CoreOS, Weaveworks, Red Hat, and Google.

Second incubated project after Kubernetes? Impressive, no?

What about Grafana?

Grafana is a multi-platform open source analytics and interactive visualization web application. It provides charts, graphs, and alerts for the web when connected to supported data sources. A licensed Grafana Enterprise version with additional capabilities is also available as a self-hosted installation or an account on the Grafana Labs cloud service. It is expandable through a plug-in system. End users can create complex monitoring dashboards using interactive query builders. Grafana is divided into a front end and back end, written in TypeScript and Go, respectively.

As a visualization tool, Grafana is a popular component in monitoring stacks, often used in combination with time series databases such as InfluxDB, Prometheus and Graphite; monitoring platforms such as Sensu, Icinga, Checkmk, Zabbix, Netdata, and PRTG; SIEMs such as Elasticsearch and Splunk; and other data sources.

Ok enough of introduction, let’s begin with steps to setup one. Continue reading “Monitoring IT Infra with Prometheus and Grafana – Part 1”

PowerShell: SharePoint Mass Deletion Alert

When working with Infrastructure and services, you would come across situations, for which you know standard solutions but due to cost reasons, you chose manual workarounds. This post would fall among those situations.

Microsoft Sharepoint services came out as a great alternative for File shares to the companies during Covid 19. Not only many automation options via Power Automate (MS Flow earlier) but it came with built-in backup via versioning (life saver I tell you). For those who were not willing to work with Web-Only option, OneDrive was another built-in capability with Windows 10 itself and was able to imitate good old file share mapping to user machines.

But there was a catch with OneDrive…..

Being a new tool, understanding the backend technology was a learning curve and during the journey many business people ended up mass deleting the content from SharePoint site thinking that they are just removing the files from their own machines or stopping the sync only. If caught in time, then SharePoint Administrator would restore the content from First stage or second stage recycle bin (if retention policies not there) or even educate the user accordingly but if not noticed in time then it might become headache. To be fair, Microsoft has many services, which can detect such scenarios and even take actions automatically if capable (like Azure Sentinel, Cloud App security and Retention Policies) but most of them come with considerable cost.

So, the question is, can our good old friend PowerShell help us in detecting such situations at least in some way so that damage may be minimized? Why not?

Continue reading “PowerShell: SharePoint Mass Deletion Alert”