by David | Jan 14, 2021 | Information Technology, PowerShell
Reading Time: 2 minutes
Do you need something to let your end-user know what's going on? Don't want your end-user to see the program running? Well, PowerShell has the ability to use the system windows notify system. AKA, the message box that pops up on the right of the screen.
The first step to make this happen is we need to add the assembly, system.windows.forms. We do this by the amazing command add-type.
add-Type -AssemblyName System.Windows.Forms
Now we have the assemblies loaded into ram, we need to create an object from those assemblies. This way we can command the object later. We do this using the New-object cmdlet. We want the notification tray icons. So we want the system.windows.forms.notifyicon.
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
You know that notifications have icons. Yep, It's from the application itself. So, if you have this function in another application, it will show that the application's icon. We call upon the System.Drawing.Icon to do this. But First, we need to know which app we are currently using. We do this with Get-Process with the ID and the current Pid.
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
What's cool about this is we are extracting the icon of the application itself with that extract associated icon. We push it into our notify icon object. Next, we want to add the Tip Icon. That's the ! ? so on and so forth. We do this by using the system.windows.forms.tooltipicon function. Believe it or not, we can use a validate set in the parameters to get the exact naming input as well. The property of the object is BalloonTipIcon.
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::$Messagetype
Now the message itself. At this point we have an icon, the notification icon and that's it. This is the meat of the notification. The propriety in the balloon object is, BalloonTipText.
$balloon.BalloonTipText = $Message
Just a quick note, to much text is ugly.
Next is the title, I like to use just the word attention as it brings the users eyes to it. The balloon's propriety for this is BalloonTipTitle.
$balloon.BalloonTipTitle = "Attention"
Finally, we want to make this thing visible. An invisible tooltip is as useful as invisible ink without a black light. As you have guessed by now, Visible is the balloon's propriety.
Now the drum roll, please! It's time to show our masterpiece that will be enjoyed by the end-user for a split second. The ShowBalloonTip(). Yes, let us do it! This is a method and will show the balloon tip for x time. This is set up in milliseconds. So, every 1000 milliseconds makes a second.
$balloon.ShowBalloonTip($Time)
Lets put it all together shall we. Behold the script of the balloon tip.
The Script
Function Invoke-SHDBallonTip {
[cmdletbinding()]
param (
[string]$Message = "Your task is finished",
[int]$Time = 10000,
[validateset("Info", "Warning", "Error")][string]$Messagetype = "Info"
)
#Adds the reqruired assembly's for the ballon tips.
Add-Type -AssemblyName System.Windows.Forms
#Creates the notifiy icon that will appear in the notification tray.
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::$Messagetype
$balloon.BalloonTipText = $Message
$balloon.BalloonTipTitle = "Attention"
$balloon.Visible = $true
$balloon.ShowBalloonTip($Time)
}
That's it, folks. Such a simple thing. Here are a few things you can do. You can change the $path to $Path = Get-Process -id $PID. Then in the balloon.icon, use $path.path. In the balloon tip title, you can put $path.description and now you have the description as the title. Other than that, it's pretty straight forward.
I would love to hear back from yall. Let me know if there is anything you would like for me to write about.
by David | Jan 7, 2021 | Information Technology, PowerShell, Resources
Reading Time: 2 minutes
This will seem dumb, but do you need a message box in your script? Normally no, but when you are using a script for an end-user, you might need it. So, let us take a look at how to do it real quick.
The Script
function Invoke-SHDMessageBox {
[cmdletbinding()]
param (
[Parameter(HelpMessage = "Message Title", Mandatory = $true)][Alias('Title', 'header')][string]$MessageTitle,
[Parameter(HelpMessage = "Message Body", Mandatory = $true)][Alias('Boby', 'Information')][string]$MessageBody,
[Parameter(HelpMessage = "Message Buttons", Mandatory = $true)][Alias('Buttons')][validateset("YesNo", "YesNoCancel", "OK", "OKCancel")][string]$MessageButtons,
[Parameter(HelpMessage = "Message Image", Mandatory = $true)][Alias('Image')][validateset("Asterisk", "Error", "Exclamation", "Hand", "Information", "None", "Question", "Stop", "Warning")][string]$Messagetype
)
Add-Type -AssemblyName PresentationCore, PresentationFramework
$ButtonType = [System.Windows.MessageBoxButton]::$MessageButtons
$MessageIcon = [System.Windows.MessageBoxImage]::$Messagetype
$Results = [System.Windows.MessageBox]::Show($Messageboxbody, $MessageboxTitle, $ButtonType, $messageicon)
Return $Results
}
The Breakdown
Lets break this guy down. First thing first, the parameters.
- MessageTitle - What will be presented as the title of the message box.
- MessageBody - The message itself.
- MessageButtons - What types of buttons.
- YesNo
- YesNoCancel
- OK
- OKCancel
- MessageType - The type of image will be presented.
All the parameters are mandatory. None can be passed by the pipeline. All are single strings instead of my normal list of strings. It's far easier that way.
Next, we need to add the assembly for the box. These are PresentationCore and PresentationFramework.
Add-Type -AssemblyName PresentationCore, PresentationFramework
Next, we just add items to the variables we will be using. The input from the parameters and their validate set. For the buttons themselves we call upon the power of the assembly we added earlier. [System.Windows.*] In the button case, the * becomes MessageBoxButton.
$ButtonType = [System.Windows.MessageBoxButton]::$MessageButtons
Then the message box image will be the same way, but with messageboximage.
$MessageIcon = [System.Windows.MessageBoxImage]::$Messagetype
Then we put it all together with the messagebox itself.
$Results = [System.Windows.MessageBox]::Show($MessageBody, $MessageTitle, $ButtonType, $messageicon)
The first part of the show is the body followed by the title, then followed by the buttons, and finally the image. Notice I pipe it into Results. I do this because we want to return those results.
That's it! Once you return the results, you can test them with an if statement. Come back Monday for a ballon notification function.
by David | Dec 23, 2020 | Information Technology, PowerShell
Reading Time: 2 minutes
Today, I needed to convert a mac address to a CIDR. The lesson I learned about this is to go back to the basics instead of looking around for a quick answer. So, to the basics first.
A mac address is a 4 octet number that represents a binary position. It ranges between 1 and 255. This means something like 255.255.255.0 is represented in binary as: 1111 1111 1111 1111 1111 1111 0000 0000
It's easier to remember the 255 number instead of all those ones and zeros. However, what's that CIDR? Well, what's a CIDR? A CIDR represents the mac address and how many positions there are. If you count the number above, it turns into 24 1s and 8 0s. Thus the CIDR is 24. Very simple.
Now, how do you change 240.0.0.0 to a cidr? You can go to a chart and look or we can use PowerShell. The first thing we need to do is split up this string.
We split the string up with a split command and tell it to split it up by the . item. We use an exit \ because the . is a special character for regex. Next, we loop through this list of octets with a foreach-object loop.
$Mask -split '\.' | ForEach-Object {
}
Then we break down each octet into a binary number. We do this with a system.convert and tell it the number we bytes we want which is 2.
[System.Convert]::ToString($_, 2)
We then pad that information because sometimes 0s will produce more useless items.
([System.Convert]::ToString($_, 2).PadLeft(8, '0'))
The code above will give us the 1s and 0s of the binary number. Now we need to count the ones. We do this by converting the string to a char array with at .toochararray(). We search the string with a where-object for the number 1 and finally count it.
(([System.Convert]::ToString($_, 2).PadLeft(8, '0')).tochararray() | where-object { $_ -eq '1' } | measure-object).count
Now we need to add each item up. We do this by adding a variable before the loop set to 0 and then add the variable to the variable and the count. Finally, after the loop, display the information.
$Cidr = 0
$Mask -split '\.' | ForEach-Object {
$Cidr = $Cidr + (([System.Convert]::ToString($_, 2).PadLeft(8, '0')).tochararray() | where-object { $_ -eq '1' } | measure-object).count
}
$Cidr
That's it yall! We count the number of 1s. That will give us our cidr count.
Script
Have fun with the script!
function ConvertFrom-SHDIPv4MaskToCidr {
param (
[string]$Mask
)
$Cidr = 0
$Mask -split '\.' | ForEach-Object {
$Cidr = $Cidr + (([System.Convert]::ToString($_, 2).PadLeft(8, '0')).tochararray() | where-object { $_ -eq '1' } | measure-object).count
}
$Cidr
}
by David | Oct 21, 2020 | Information Technology, PowerShell, Resources
Reading Time: 4 minutes
Ever need a service that copies a single folder to multiple locations at once? This script will do just that. It will copy a single location to more than one location and even log the outcomes accordingly. Thus, you will be able to set this one up as a task and run it every so often.
Invoke-SHDMoveFiles {
<#
.SYNOPSIS
Copy files from target source folders to a set of target folders.
.DESCRIPTION
Copys from multiple source folders to a select target folders. This allows archiving to occur.
The folders are not auto created. Thus, the folders must exists. The script also adds a log file of any error
messages you see. See notes for more details.
.PARAMETER SourceFolder
[String[]] SourceFolder is an Array of Strings of target folders to move the files from.
.PARAMETER TargetFolder
[String[]] TargetFolder is an Array of strings where the files will be copied to. This is not a one for one ratio. Thus all files will exist inside the targetfolder.
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\','C:\tbc\tmp\Source Folder 2\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'
Moves all files from the source folder 1 and 2 to target folders 1 and 2.
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'
Moves all files from the source folder 1 to target folders 1 and 2.
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\' -TargetFolder 'C:\tbc\tmp\Target Folder\'
Moves all files from the source folder 1, 2, and 3 to the target folder
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\','C:\tbc\tmp\Source Folder 2\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\' -Recurse
Moves all files under Source folder 1 and 2 to target folders 1 and 2.
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\' -Recurse
Moves all files under source folder 1 to target folders 1 and 2.
.EXAMPLE
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\' -TargetFolder 'C:\tbc\tmp\Target Folder\' -Recurse
Moves all files under source folder 1, 2, and 3 to the target folder
.INPUTS
[String] Folder Paths
.OUTPUTS
No Output
.NOTES
Author: David Bolding
Date: 8/16/2020
Purpose: Moving files around and making archives
Min Powershell Version: 4
This command generates error logs located c:\tbc\SHDMoveFileLog.txt. The error logs structure is broken up in 4 sections seperated with by ;
1;$((Get-Date).tostring());Catch;$($_.Exception)
1 - The placement of the error physically inside the script.
2 - The date and time of the error
3 - What type of error
4 - Information about the error
Run this command inside a scheduled task every 5 minutes to copy files needed to required locations. Make sure the
tasks running user has full access right to the file locations provided.
.LINK
https://bolding.us
#>
[cmdletbinding()]
param (
[Parameter(HelpMessage = "Source Folder", Mandatory = $True)][string[]]$SourceFolder,
[Parameter(HelpMessage = "Target Folders", Mandatory = $True)][string[]]$TargetFolder,
[parameter(HelpMessage = "Recurse the Source Folder")][switch]$Recruse
[parameter(HelpMessage = "Log Location")][string]$Logs = "C:\tmp\SHDMoveFileLog.txt"
)
#Tests to see if local c:\tbc exists, if it doesn't, it will create the path.
if (Test-Path (Split-Path $logs)) { "" >> $logs } else { mkdir (Split-Path $logs) }
#Start with the first source
foreach ($Source in $SourceFolder) {
#Tests to see if the source path exists.
if (Test-Path -Path $Source) {
#Grabs the required files from the first source path. It only grabs files.
#Also checks if there is a recruse flag. If so, recurses accordingly.
if ($Recruse) { $files = Get-childitem -Path $Source -File -Recurse } else { $files = Get-childitem -Path $Source -File }
#Next we sort through the files in question
foreach ($File in $files) {
#Create a test bool.
$success = $false
#Starts the target folder sorting
foreach ($Target in $TargetFolder) {
#Tests to see if target folder exists. If not, logs.
if (Test-Path -Path $target) {
#Enter a try catch to copy the files
try {
#Copys a single file to target folder. Overwrites any other there without confirmation
#No Confiramtion due to the lack of human Interaction.
Copy-Item -Path $file.fullname -Destination $Target -Force -Confirm:$false
#If no error so far, sets the success bool to true
$success = $true
}
catch {
#a failure occured, thus we set the success bool to false
$success = $false
#We log the error. This is the first log that shows up in the system.
#We date it.
#We state it's a catch
#Then we give the reason the try catch gives.
"1;$((Get-Date).tostring());Catch;$($_.Exception)" >> $logs
}
}
else {
#We log the fact that we can't reach the target location
"2;$((Get-Date).tostring());CanNotFind;$Target" >> $logs
}
}
#We test the bool for success.
if ($success -eq $true) {
try {
#if successful we remove the file.
Remove-Item -Path $file.FullName -Force -Confirm:$false
}
catch {
#If we can't remove the file we log the reason why.
"3;$((Get-Date).tostring());Catch;$($_.Exception)" >> $logs
}
}
}
}
else {
#We log the fact we can't reach the source location.
"4;$((Get-Date).tostring());CanNotFind;$Source" >> $logs
}
}
}
Examples
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder\' -TargetFolder 'C:\tbc\tmp\Target Folder\','C:\tbc\tmp\Target Folder 2\'
Moves all files from the source folder 1 to target folders 1 and 2. This is good for a backup of files. What I use this for is a custom software at work. It other products drop to location 1. Then it has a backup folder and the system folder. There we go, good as gold.
./Invoke-SHDMoveFiles -SourceFolder 'C:\tbc\Tmp\Source Folder 1\','C:\tbc\Tmp\Source Folder 2\','C:\tbc\Tmp\Source Folder 3\' -TargetFolder 'C:\tbc\tmp\Target Folder\' -Logs c:\tmp\Combiner.txt
Moves all files from the source folder 1, 2, and 3 to the target folder. It also logs any errors. This is useful when trying to come folders together.
Notes
- This script was designed to be ran as a task. If it doesn't grab a file the first time around, it will grab it the second time around. It does this by using the try catch. It trys to move a file. if it doesn't, it logs it and leaves it. We do that because a lot of times these files are being created and have a lock state. It's best not to move them during that state.
- This script auto logs into the c:\tmp location if you don't state otherwise when things go wrong.
by David | Oct 16, 2020 | Information Technology, PowerShell, Resources
Reading Time: 3 minutes
This little script will allow you to check a folder location, if that folder location has x number of files, it will restart the service of your choice on a target computer. Then it will log that information and finally it will email someone this information.
function Invoke-FileServiceIntegrityCheck {
<#
.SYNOPSIS
Checks a file path for number of files. If greater, restarts a service and emails a message and logs a log.
.DESCRIPTION
Checks a file path for a set number of files that are predefined by the end user. If the file count is greater, it will restart a service. It will log the results and email a person stated by the end user.
.PARAMETER SearchPath
Target Path to scan for file count.
.PARAMETER FileCount
File count that will be used to determine the request size.
.PARAMETER ComputerName
Target Computer Name that the service lives on.
.PARAMETER ServiceName
Name of the service to restart.
.PARAMETER SendReportTo
The email address to which the email report will be sent to.
.PARAMETER EmailDomain
The domain name. For example, example.local
.PARAMETER MailServer
The mail server to send the email from.
.PARAMETER Recurse
Recurses the file search path.
.PARAMETER Credential
The Credentials, if needed for the email server.
.EXAMPLE
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com -Credential (Get-Credential)
Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
Then it will send an bob@example.com from watcheye@example.com using mail.example.com and the credentials provided.
.EXAMPLE
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName intergrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com
Checks the folder \\server1\share to see if there is 25 or more files. If it does, it will restart the service named intergrator on server 2.
Then it will send an bob@example.com from watcheye@example.com using mail.example.com. Email will be sent from the server. If no relay is setup, the server will reject the email.
.INPUTS
[none]
.OUTPUTS
email and log
.NOTES
Author: David Bolding
Date: 10/14/2020
.LINK
https://github.com/rndadhdman/PS_Super_Helpdesk
#>
[cmdletbinding()]
param (
[parameter(HelpMessage = "Folder Search Path", Mandatory = $True)][string]$SearchPath,
[parameter(HelpMessage = "File Count", Mandatory = $True)][int]$FileCount,
[parameter(HelpMessage = "Computer That the server Lives", Mandatory = $True)][String]$ComputerName,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$ServiceName,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$SendReportTo,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$EmailDomain,
[parameter(HelpMessage = "Service Name", Mandatory = $True)][String]$Mailserver,
[parameter(HelpMessage = "Recurse the Search")][switch]$Recurse
[Parameter(HelpMessage = "Allows for custom Credential.")][System.Management.Automation.PSCredential]$Credential
)
#Checks Files
if ($Recurse) { $Files = Get-ChildItem "$SearchPath" -Recurse } else { $Files = Get-ChildItem "$SearchPath" }
if ($Files.Count -ge $FileCount) {
$StopTime = Get-date
Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Stop-Service
sleep 30
Get-Service -ComputerName mtxs-lifetecapp -Name "Interlink" | Start-Service
$StartTime = Get-date
sleep 30
if ($Recurse) { $Files2 = Get-ChildItem "$SearchPath" -Recurse } else { $Files2 = Get-ChildItem "$SearchPath" }
if (!(Test-Path c:\logs)) {mkdir c:\logs}
#Gathers Log Info
$Log = [pscustomobject]@{
ComputerName = $ComputerName
ServiceName = $ServiceName
SearchPath = $SearchPath
StopFileCount = $Files.count
ServiceStopTime = $StopTime.ToString()
ServiceStartTime = $StartTime.tostring()
StartFileCount = $Files2.count
CallingComputer = $env:COMPUTERNAME
}
$Log | Export-csv c:\temp\Invoke_FileServiceIntegrityCheck_log.csv -Append
#Builds EMail
$ErrorHtmlbody = @"
<html>
<head>
<style>
body {
Color: #252525;
font-family: Verdana,Arial;
font-size:11pt;
}
h1 {
text-align: center;
color:#C8102E;
Font-size: 34pt;
font-family: Verdana, Arial;
}
</style>
</head>
<body>
<h1>$ServiceName Restarted</h1>
<hr>
<br>
Dear Watcher,<br>
<br>
During my last check there were <b>$($Files.Count)</b> files inside <i>$SearchPath</i>. The Integrator was stopped on <b>$($StopTime.tostring())</b> and started on <b>$($StartTime.tostring())</b>. There are now <b>$($files2.count)</b> in <i>$SearchPath</i>. Please investigate on why the $ServiceName failed on $ComputerName.<br>
<br>
<br>
Thank you,<br>
<i>$ServiceName WatchEye</i><br>
<br>
<hr>
</body>
</html>
"@
if ($PSBoundParameters.ContainsKey('Credential')) {
Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver -Credential $Credential
} else {
Send-MailMessage -To $SendReportTo -From "Service Watch Eye <WatchEye@$EmailDomain>" -Subject "$ServiceName Failure" -BodyAsHtml $ErrorHtmlbody -SmtpServer $Mailserver
}
}
}
Example
Invoke-FileServiceIntegrityCheck -SearchPath \\server1\share -FileCount 25 -ComputerName server2 -ServiceName integrator -SendReportTo bob@example.com -EmailDomain example.com -Mailserver mail.example.com
In this example, we will search for the share on server1 if it has more than 25 files. If it does, then we will restart the integrator on server2. Finally, we will email bob@example.com with an email from watcher@example.com using the mail server mail.example.com.