Sometimes when working on a PowerShell problem, you might have to come to the conclusion that PowerShell is not the right tool for the job. There are some tasks or applications that simply don't lend themselves to automation or PowerShell. This isn't necessarily a limitation in PowerShell, nor would I say it is a deficiency in the application at hand. It is just the nature of IT.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
That said, there may be situations where you still want to use PowerShell, even if the experience is less than optimal. So let's have some fun with this today. For a number of versions of Windows client's there has been a little utility called Sticky Notes.
This is an electronic version of Post-It Notes. Wouldn't it be nice to be able to use PowerShell to take advantage of the utility? Sadly, this application is written in such a way that nothing is exposed that can be automated, either through COM or .NET. So we're left with a drastic measure of sending keys to the application.
As in the days of VBScript, sending keys is a risky proposition because you can't always guarantee that the application in question will have focus. But let's try. First, we need to start the program simply by invoking the command stikynot.
To send keys I'm going to use the .NET Framework. So to be on the safe side, I'm going to add some necessary assemblies just in case. It won't matter if they are already loaded.
[void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic") [void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
Now to activate the window. You can do this by the window title.
PS D:\temp> get-process stikynot | select mainwindowtitle MainWindowTitle --------------- Sticky Notes
Or the process ID. I'll use the title.
[Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes")
While the window has focus, I can send keys to it.
[System.Windows.Forms.SendKeys]::SendWait("Get milk")
And that's all there is to it! Sticky Notes have a number of keyboard shortcuts you can send using this technique. I found a page at http://www.door2windows.com/list-of-all-keyboard-shortcuts-for-sticky-notes-in-windows-7/ that listed many of them.
But you know I'm not going to stop here. I built a module called StickyNotes.
#requires -version 3.0 #StickyNotes.psm1 <# a set of functions for using Sticky Notes in Windows Learn more: PowerShell in Depth: An Administrator's Guide (http://www.manning.com/jones6/) PowerShell Deep Dives (http://manning.com/hicks/) Learn PowerShell in a Month of Lunches (http://manning.com/jones3/) Learn PowerShell Toolmaking in a Month of Lunches (http://manning.com/jones4/) **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** #> #load necessary assemblies just in case [void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic") [void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms") #sleep interval to allow stikynot process to start $sleep = 150 #private function for formatting the note Function _FormatNote { [cmdletbinding(DefaultParameterSetName="__AllParameterSets")] Param( [string]$Text, [switch]$Bold, [switch]$Italic, [switch]$Underline, [Parameter(ParameterSetName="Center")] [switch]$Center, [Parameter(ParameterSetName="Right")] [switch]$Right, [switch]$Append ) Write-verbose "Activating note" #grab the program [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") #add formatting if ($Bold) { [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^b") } if ($Italic) { [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^i") } if ($Underline) { [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^u") } if ($Center) { [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^e") } if ($Right) { [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^r") } if ($append) { #jump to the end of existing text Write-verbose "Appending" [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("{Down}") } #send the text if ($text) { #copy text to clipboard and paste it. Faster than trying to send keys $text | clip [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") [System.Windows.Forms.SendKeys]::SendWait("^v") [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") #give the paste a moment to complete start-sleep -Milliseconds 50 [System.Windows.Forms.SendKeys]::SendWait("{Enter}") } #if $text [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") Start-Sleep -Milliseconds 50 [System.Windows.Forms.SendKeys]::SendWait("{Down}") Write-Verbose "Finished formatting note" } #end private function Function New-StickyNote { <# .Synopsis Create a new sticky note. .Description This command will create a new sticky note. You can specify additional formatting options. .Parameter Center Align text center. The default is left. .Parameter Right Align text right. The default is left. .Example PS C:\> new-stickynote "pickup milk on the way home" -bold .Notes Last Updated: Sept. 25, 2014 Version : 0.9 #> [cmdletbinding(DefaultParameterSetName="__AllParameterSets")] Param( [Parameter(position=0,Mandatory=$True,HelpMessage="Enter text for the sticky note")] [string]$Text, [switch]$Bold, [switch]$Italic, [switch]$Underline, [Parameter(ParameterSetName="Center")] [switch]$Center, [Parameter(ParameterSetName="Right")] [switch]$Right ) Write-Verbose "Starting $($MyInvocation.MyCommand)" #if Sticky Note is already running switch to Add-StickyNote $proc = Get-Process -Name stikynot -ErrorAction SilentlyContinue if ($proc) { Write-Verbose "Sticky Note already running. Calling Add-StickyNote" Add-StickyNote @PSBoundParameters #bail out of this function Return } else { #start the program Try { Write-Verbose "Starting new process" Start-Process stikynot #give it a moment to complete Write-Verbose "Sleeping $sleep ms" Start-Sleep -Milliseconds $sleep } catch { Throw } } #if $proc #format the note _FormatNote @PSBoundParameters #return focus to originating app [Microsoft.VisualBasic.Interaction]::AppActivate($pid) Write-Verbose "Ending $($MyInvocation.MyCommand)" } #end function Function Add-StickyNote { <# .Synopsis Add a new sticky note. .Description This command will create a add sticky note. You can specify additional formatting options. If there are no existing sticky notes, this command will create one. .Parameter Center Align text center. The default is left. .Parameter Right Align text right. The default is left. .Example PS C:\> add-stickynote "pickup milk on the way home" -bold .Notes Last Updated: Sept. 25, 2014 Version : 0.9 #> [cmdletbinding(DefaultParameterSetName="__AllParameterSets")] Param( [Parameter(position=0,Mandatory=$True,HelpMessage="Enter text for the sticky note")] [string]$Text, [switch]$Bold, [switch]$Italic, [switch]$Underline, [Parameter(ParameterSetName="Center")] [switch]$Center, [Parameter(ParameterSetName="Right")] [switch]$Right ) Write-Verbose "Starting $($MyInvocation.MyCommand)" #start the program if not running Try { if (-Not (Get-Process -Name stikynot -ErrorAction SilentlyContinue)) { Write-Verbose "Starting Sticky Note" Start-Process stikynot #give it a moment to complete Write-Verbose "Sleeping $sleep ms" Start-Sleep -Milliseconds $sleep $Existing = $False } else { #create a flag to know that I can use an existing process Write-Verbose "Re-using existing Sticky Note Process" $Existing = $True } #grab the program [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") } catch { Throw } If ($Existing) { #Add a new note Write-Verbose "Add a new note" [System.Windows.Forms.SendKeys]::SendWait("^n") Start-sleep -Milliseconds $sleep } #format the note _FormatNote @PSBoundParameters #return focus to originating app [Microsoft.VisualBasic.Interaction]::AppActivate($pid) Write-Verbose "Ending $($MyInvocation.MyCommand)" } #end function Function Set-StickyNote { <# .Synopsis Set text and style for a sticky note. .Description This command will set the text and style for a sticky note. If there are multiple notes, this command will set the one that has focus, which you must do manually. The default is the last note created. The style parameters like Bold behave more like toggles. If text is already in bold than using -Bold will turn it off and vice versa. .Parameter Center Align text center. The default is left. .Parameter Right Align text right. The default is left. .Parameter Append Append your text to the end of the note. Otherwise existing text will be replaced .Example PS C:\> set-stickynote "pickup milk on the way home" -bold -underline -append .Notes Last Updated: Sept. 25, 2014 Version : 0.9 #> [cmdletbinding(DefaultParameterSetName="__AllParameterSets")] Param( [Parameter(position=0)] [string]$Text, [switch]$Bold, [switch]$Italic, [switch]$Underline, [Parameter(ParameterSetName="Center")] [switch]$Center, [Parameter(ParameterSetName="Right")] [switch]$Right, [switch]$Append ) Write-Verbose "Starting $($MyInvocation.MyCommand)" #start the program if not running Try { #grab the program [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") #select existing text [System.Windows.Forms.SendKeys]::SendWait("^a") } catch { Throw } #format the note _FormatNote @PSBoundParameters #return focus to originating app [Microsoft.VisualBasic.Interaction]::AppActivate($pid) Write-Verbose "Ending $($MyInvocation.MyCommand)" } #end function Function Remove-StickyNote { <# .Synopsis Remove a new sticky note. .Description This command will remove the sticky note that has focus. If you have more than one note, the last one created has focus, unless you manually select a different one. If you kill the stikynot process, the next time you create a note, any previously created notes will return. .Example PS C:\> remove-stickynote .Notes Last Updated: Sept. 25, 2014 Version : 0.9 #> [cmdletbinding(SupportsShouldProcess)] Param() Write-Verbose "Starting $($MyInvocation.MyCommand)" $proc= Get-Process stikynot -ErrorAction Stop if ($proc) { #grab the program [Microsoft.VisualBasic.Interaction]::AppActivate("Sticky Notes") if ($PSCmdlet.ShouldProcess(($proc| Out-String).Trim())) { [System.Windows.Forms.SendKeys]::SendWait("^d") } #whatif } else { Write-Verbose "No sticky notes seem to be running" } #return focus to originating app [Microsoft.VisualBasic.Interaction]::AppActivate($pid) Write-Verbose "Ending $($MyInvocation.MyCommand)" } #end function #define aliases Set-Alias -Name rn -Value Remove-StickyNote Set-Alias -Name an -Value Add-StickyNote Set-Alias -Name sn -Value Set-StickyNote Set-Alias -Name nn -Value New-StickyNote Export-ModuleMember -Alias * -Function "*-StickyNote"
The module includes several functions for creating, adding, setting and removing sticky notes. The tricky part was getting the timing right. You might need to adjust some sleep values, or be prepared to re-run a command. I would not build any mission critical processes around anything that uses send keys techniques as it isn't reliable. But it sure is fun!
You might even use it like this:
start-job { $paramHash = @{ LogName = "System" newest = 10 ComputerName = "chi-dc01","chi-dc02","chi-dc04","chi-core01","chi-hvr2" EntryType = "Error","warning" } Get-EventLog @paramHash New-StickyNote "The event log job has completed" -Bold }
At the end of the command, a sticky note pops up letting you know the deed is done. You might see an error about a missing process, but you can ignore it. Or maybe you simply need a reminder to pickup that loaf of bread or your child on the way home.
Sometimes PowerShell isn't the best tool for the job, but I think it can always be fun. Let me know what you think of my little project.
Have a great weekend.