PowerShell: Start all VMWare services, self elevate to admin

For reasons, all my VMWare services are set to manual startup.  I like it this way.

I wanted an easy way to start all the services so naturally, a PowerShell script was required.  However the services can’t be started by PowerShell without elevated privileges, and I usually work in a non-elevated ISE.  So this version of the script self-elevates, saving me precious seconds.

# Start All VMware services V2

# Check if the current user role is in the local computer administrator role

            # get-service -displayname 'VMware*' | %{Stop-Service -name $_.Name}

If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
    [Security.Principal.WindowsBuiltInRole] "Administrator")){
               # Not running as administrator

                # Create a new process object that starts PowerShell
                   $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
                   # Specify the current script path and name as a parameter
                   $newProcess.Arguments = "& '" + $script:MyInvocation.MyCommand.Path + "'"
                   # Indicate that the process should be elevated
                   $newProcess.Verb = "runas";
                   # Start the new process
        # Elevated Code - will only run if IS Administrator 
            # Set some colors so its clear what is going on
                $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
                $Host.UI.RawUI.BackgroundColor = "DarkRed"

            # start all the VMware services
                $s = get-service -displayname 'VMware*' # i should really list the services in the right startup order
                write-host $s

                    foreach ($svc in $s){

                        write-host $svc.name 'is' $svc.Status
                        if($svc.Status -ne "Running")
                            write-host 'Starting: ' $svc.Name
                                #Stop-Service $svc.name
                                #set-service $svc.Name -StartupType Manual
                            Start-Service $svc.Name

                             # Do something else if you want

                #start "C:\Program Files (x86)\VMware\VMware Workstation\vmware.exe"      

 # Re-present the services again

     get-service -displayname 'VMware*'
        Write-Host "Sleeping 60" 
        start-sleep -seconds 60




PowerShell: JIRA Archiver v3

This web scraper is a little odd because it had to run within a very constrained environment.

Essentially it attempts to load URLs consisting of a prefix and sequential numerical suffix in the format;


then prints the webpage to PDF via the default printer, and downloads a zip file from the URL found in each page’s ‘aszip’ element.


# JIRA Archive v3.0
# For archiving JIRA entries and zip attachments

# Its a quick and dirty web scraper 
# Not the most ideal way I would usually do this but this one needed 
#  to work with several corporate SOE and security restrictions

# SYNOPSIS -------------------------------------------------

# 	Needed a way to download and archive all JIRA items and attachments.
#	The JIRA setup does not allow bulk extraction or archiving.
#       The solution has to operate with only the same access as a non-admin client user.

# CHALLENGES -----------------------------------------------

#	Corporate SOE prevents IE from auto downloading without user prompting, Chrome however allows it.
#	Invoke-WebRequest doesnt work (powershell is only v2, possibly other causes of this)
#	Resulting archive needs to be seen to maintain integrity of source without tamper (as much as possible)
#	(Hence print to PDF rather than save as HTML)
#   Urgency.  
# Set the target JIRA number range
    $from_JIRA = 1
    $to_JIRA = 300

# Throtle the time between downloads 

    $throtle = 5 # in seconds

# -------------- Functions

            # TestFileLock
            function TestFileLock {
              param (

              $oFile = New-Object System.IO.FileInfo $Path

              if ((Test-Path -Path $Path) -eq $false) {
                return $false

              try {
                $oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)

                if ($oStream) {
              } catch {
                # file is locked by a process.
                return $true

# -------------------------------------------------

#Main Loop

for ($current_JIRA=$from_JIRA; $current_JIRA -lt $to_JIRA; $current_JIRA ++){

    Write-Host ('Processing JIRA-' + $current_JIRA)

    $website = ("https://url/browse/JIRA-" + $current_JIRA)
        #using ie com object because invoke-webrequest doesnt work 
        $ie=new-object -com internetexplorer.application 
        #Give the website a chance to load (note: might replace this with a page load return code check) 
        start-sleep -seconds 10
        #Print to default printer which is set to PDFCreator
        #Scrape the 'download all attachments' link url from the element id
        $aszipurl = $ie.Document.getElementById('aszip').href
        write-host "attachments zip url: " $aszipurl
        if ($aszipurl -ne $null){
        #if zip file exists pass it to chrome because cant get IE to download without prompting
        Start-Process "chrome.exe" $aszipurl}

     # wait for pdf to exist and then rename it 
     $testpath = 'C:\Users\user\Downloads\current.pdf'

      while ((Test-Path $testpath) -ne $true){
         write-host "Waiting for pdf to exist..."
         Start-Sleep -seconds 5

     $newpath = 'C:\Users\user\Downloads\Named\JIRA_' + $current_JIRA + '.pdf'

     #make sure the pdf has finished writing first
     while ((TestFileLock($testpath)) -eq $true){

        write-host "Waiting for pdf to be complete ..."
         Start-Sleep -seconds 5

     #now move the file
     write-host "Moving PDF"
     Move-Item -path $testpath -Destination $newpath

     #Close the ie instance

     write-host "Sleeping " $throtle
     start-sleep -Seconds $throtle



ideas are in and of themselves worthless

A very good friend said to me once, and i’m paraphrasing for brevity,

“…ideas are worthless, they are everywhere, they are free.  It’s the ability to get them done that has any value.”

I’ve met plenty of people with good ideas.  People who can drive a good idea home to delivery are, however, of much rarer occurrence.


PowerShell: some simple snippets

Some simple PowerShell snippets I find useful.

Not worthy of their own pages, but useful nevertheless.

Show the start time of process

Get-Process | Select-Object id, starttime, name | Sort-Object starttime

Search inside a set of files for a string

$searchFiles = Get-ChildItem F:\files\ -Filter *.ext -Recurse
$searchFiles | Select-String -Pattern "find this string"





PowerShell: RoboClicky V2 Mouse Move, Click, Screen Capture

This script does a few things.  I will break it apart into separate posts that deal with each bit, but for now here is the whole lot.

The purpose of this script is to;

  • move the mouse to specific screen coordinates
  • click mouse buttons at those coordinates in order to drive an application
  • give the application time to respond
  • take a screenshot
  • save the screenshot as required
  • cater for multiple screens in a horizontal configuration

There are some functions in this one not specifically related to the objectives, but for reasons I won’t go into here, I generally try to keep all scripts as self-contained as possible.  I will try to break this out when I get time.

#region Includes
# ======================  Add some required bits and import some DLLs

     # System drawing and windows forms

     # Some mousey things

            [DllImport("user32.dll",CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)]
             public static extern void mouse_event(long dwFlags, long dx, long dy, long cButtons, long dwExtraInfo);

        $SendMouseClick = Add-Type -memberDefinition $signature -name "Win32MouseEventNew" -namespace Win32Functions -passThru 
     # Stopwatch for timing the whole process 
        $SWScriptTime = [Diagnostics.Stopwatch]::StartNew()

#endregion Includes

#region Variables
    # ====================== Define Some Screen Variables

        # Screen size related variables 

            $baseScreenSizeX = 1920 #pixles
            $baseScreenSizeY = 1080 #pixles
            $sleepPeriod = 20 #seconds 
            $baseimagepath = "D:\RoboClicky\images\"
        # Log File Variables    
	        $appVersion = "RoboClicky Version 2"
            $logMonth = (Get-Date -Format "yyyy-MM-")
	        $logDay = (Get-Date -Format "dd-")
            $basePath = ""
            $logFile = ""
            $logDestinationDetail = $basepath + "D:\RoboClicky\"+$logmonth+$logday+"RoboClicky_Detail_Log.txt"   

        # Temporary session log

            $global:currentLog = ""

#endregion Variables

    # ====================== FUNCTIONS

        #region GenericFunctions 
        # I use these in many of my scripts

            # ====================== Format Date Function

                   function GetDateFormatted(){
                        Return (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
                   function GetDateFormattedForPaths(){
                        Return (Get-Date -Format "yyyy-MM-dd HHmmss")

            # ====================== Logging Function

                    function LogThis($logthisstring){
                                $logEntry = (GetDateFormatted)  + "# " + $logthisstring
                                $global:currentLog = $global:currentLog + "`n" + $logentry
                                $logEntry | Out-File $logDestinationDetail -Append
                                $logEntry | Out-host

            # ====================== Path Testing Function 
              function CheckFileExists($checkPath){
                $filexists = (test-path ($checkPath))
                logthis("Check Path: " + $checkPath + " | file exists = " + $filexists)
                return $filexists

            # ====================== Exit Block

            $ExitNow = {
                # find me a line Morpheus 
                     logthis("EXIT NOW! Script complete in " + $SWScriptTime.Elapsed + " ... welcome to the real world Neo ...")
                    $global:currentLog | Out-File $currentLogPath -Force

        #endregion GenericFunctions 

#region RoboClickyFunctions

    # ====================== Return mouse to 0,0 coordinates

    function mooseHome(){
        logthis ("F: moving mouse now to coordinates: 0,0")
        [Windows.Forms.Cursor]::Position = "0,0"


    # ====================== Move mouse to specified coordinates and sleep for sleep period

    function mooseMove($screenNumber, $xCoords, $yCoords, $label, [boolean]$isrelative, $loadWaitPeriod){

        # NOTE::: This code only handles displays that have left to right numbering.  Future upgrade will include full relative screen handling.  Because it would be awesome!

            #Log some things 
                logthis ("F: mooseMove function called with paramaters;")
                logthis ("     screenNumber = "+$screenNumber)
                logthis ("     xCoords = "+$xCoords)
                logthis ("     yCoords = " +$yCoords)
                logthis ("     label = " + $label)
                logthis ("     isrelative = " + $isrelative)
                logthis ("F: calculating absolute coordinates")

            #Set Absolute X coordinates of the screen (which are relative to the entire screen canvas)
            $absolutexCoords = $xCoords + ($baseScreenSizeX * $screenNumber)
            $absoluteyCoords = $yCoords

            $coords = "$($absolutexCoords),$($absoluteyCoords)"
                logthis ("      ... "+$coords)    
                logthis ("F: moving mouse now to screen " + $screenNumber + ", relative coordinates: x=" + $xCoords + ", y=" + $yCoords + ", absolute coordinates: "+$coords)

                # move the mouse 
                [Windows.Forms.Cursor]::Position = $coords
                # perform left mouse click using mooseClick function

            logthis ("F: sleeping for: " + $sleepPeriod +" seconds to allow for screen rendering")
            sleep $loadWaitPeriod



    # ====================== Perform left mouse click

    function mooseClick
        logthis ("F: left mouse clicking now")
            $SendMouseClick::mouse_event(0x00000002, 0, 0, 0, 0); #Left Mouse Down
            $SendMouseClick::mouse_event(0x00000004, 0, 0, 0, 0); #Left Mouse Up

    # ====================== Grab ScreenShot of screen X

    function screenshot($displaynumber,$screenshotname) {
       logthis ("F: Performing screen capture of display "+$displaynumber)

            #$bounds = [Drawing.Rectangle]::FromLTRB($screen.Left, $screen.Top, $screen.right, $screen.bottom)
            #$bounds = [Drawing.Rectangle]::FromLTRB(-236, -1080, 1684, 0)

            #calculating cooridinates for screen capture
                logthis("#calculating cooridinates for screen capture")

                $captureLeft = $baseScreenSizeX * $displaynumber
                $captureTop = 0 # fixed value in this version
                $captureRight = ($baseScreenSizeX * $displaynumber) + 1920
                $captureBottom = 1080 # fixed value in this version

                $bounds = [Drawing.Rectangle]::FromLTRB($captureLeft, $captureTop, $captureRight, $captureBottom)

                $bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height

                $graphics = [Drawing.Graphics]::FromImage($bmp)

                logthis("F: Screen capturing from absolute range:")
                logthis("     Left: "+$captureLeft)
                logthis("     Top: "+$captureTop)
                logthis("     Right: "+$captureRight)
                logthis("     Bottom: "+$captureBottom)

                # create image 

                   $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
                   $path=$baseimagepath + $screenshotname + ".png"
                   logthis ("F: Saving screen capture as "+$path)


    function refreshIE($refreshStringRef)

    # Function to refresh IE window with a specific name
        logthis("F: Refreshing IE Where name of window contains '" + $refreshStringRef +"'")
        $shell = new-object -ComObject shell.application
        $shell.windows() | 
                Where-Object { $_.document.url -like $('*' + $refreshStringRef + '*')} | 
                ForEach-Object { $_.refresh() }


#endregion RoboClickyFunctions

#endregion FUNCTIONS

#region Mainline

    # Step 1 - Refresh IE window containing application visualisation

        refreshie -refreshStringRef "ie-application-window-name"
        logthis ("waiting for ie-application-window-name")
        sleep 30

    # Step 2 - Move mouse around the visualisation using the mooseMove function, then snap an image of it using the screenshot function. 
        # Zone A

        mooseMove -screenNumber 6 -xCoords 685 -yCoords 21 -label "Zone A" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_A"

        # Zone B

        mooseMove -screenNumber 6 -xCoords 797 -yCoords 21 -label "Zone B" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_B"

        # Zone C

        mooseMove -screenNumber 6 -xCoords 908 -yCoords 21 -label "Zone C" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_C"

        # Zone D

        mooseMove -screenNumber 6 -xCoords 1020 -yCoords 21 -label "Zone D" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_D"

        # Zone E

        mooseMove -screenNumber 6 -xCoords 1131 -yCoords 21 -label "Zone E" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_E"

        # Zone F

        mooseMove -screenNumber 6 -xCoords 1242 -yCoords 21 -label "Zone F" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_F"

        # Zone G

        mooseMove -screenNumber 6 -xCoords 1354 -yCoords 21 -label "Zone G" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_G"

        # Zone H

        mooseMove -screenNumber 6 -xCoords 1465 -yCoords 21 -label "Zone H" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_A"

        # Zone etc...

        mooseMove -screenNumber 6 -xCoords 1576 -yCoords 21 -label "Zone etc... and so on" -isrelative $true -loadWaitPeriod 25
        screenshot -displaynumber 6 -screenshotname "Dashboard_Zone_Summary"


#endregion Mainline









PowerShell: DVD copy automation script V2.0


I have a bunch of DVD’s that I want to archive to hard-disk so I can dispose of the DVDs.

Automate the process as much as possible.

Note: This is not a DVD copy protection crack, the DVD’s have to be copyable by the OS.


This script will wait for the DVD volume name to be NOT NULL

When a DVD is inserted, the script automatically creates a new folder in the destination root path, using the DVD name as the destination folder name.

After completing the copy it ejects the DVD drive and loops back into the waiting state.


  • Disk insertion detection (not based on auto-run)
  • Destination folder name duplication protection via folder-name suffix (this prevents DVDs of the same name overwriting each-other)
  • Simple copy integrity check using source and destination length comparison
  • Speech so I can hear it from the next room when the DVD needs to be changed out

To Do

I’m wondering if using [System.IO.StreamWriter would be faster, but I think the bottleneck would be in the DVD Read, not the disk write.  Speculation only.  Next time I use the script I’ll make that modification and see how it goes.


My scripts are open source, free to use, come with neither warranty as to optimal performance nor suggestion that they are necessarily the best way to achieve anything.

<# DVD Copy Script V 2.0
     This version speaks so I can hear it from the next room when it needs a new disk

 SYNOPSIS ----------------------------------------------------------------------

     I have a bunch of DVD's that I want to archive to Hard Disk so I can dispose of the DVDs
     I want to automate it as much as possible.

     This script will wait for the DVD volumename to be not null
     When a DVD is inserted it automatically copies the DVD to a destination folder root
      using the DVD name as a new destination sub-folder
     After completing it pops open the dvd drive and loops back into waiting for disk mode
     This version uses speach so I can hear it from another room!

 FEATURES ---------------------------------------------------------------------

    * Speach
    * Destination folder name duplication protection
    * Simple copy integrity check using length


# INCLUDES ----------------------------------------------------------------------

    # add speach object

    Add-Type -AssemblyName System.speech
    $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer

# FUNCTIONS --------------------------------------------------------------------- 

    function popdisk(){
        (new-object -COM Shell.Application).NameSpace(17).ParseName($dvdpath).InvokeVerb('Eject')

    function testpath($tpath){
              if ((test-path $tpath) -ne $true) {
              $speak.Speak('path does not exist ... all is done here"')
              return $tpath
                   $speak.Speak('path exists, adding suffix and retesting') 
                    $tpath += "_1"
                    $speak.Speak("new foldername is "+ $tpath)
                     testpath "$tpath"

    function CopyDVD{

    # Drives and Paths (omit \)
        $dvdpath = 'I:' #no slash
        $destpath = 'E:\DVDCOPY' #no slash


            #Clear variables
            $volumename = $null
            $foldername = ""
            $drive =""

            $speak.Speak('Waiting for disk')
            "  Waiting for disk ... " | Out-Host

            #loop while disk volumename is null
            $drive = Get-WmiObject -Class Win32_CDROMDrive
            $volumename = $drive.volumename
            }while ($volumename -eq $null)

            #disk detected get source details
                $speak.Speak('Disk detected, obtaining details.')
                $foldername = $volumename.ToString()
                #Check for duplicate folder and suffix number if folder exists

                $speak.Speak('Disk name is ' + $volumename.ToString())
                $source = Get-ChildItem "$dvdpath\VIDEO_TS\" -recurse | Measure-Object -Property length -Sum 
                $sourceLength = $source.Sum
                start-sleep -seconds 10
                $speak.Speak('Copying Disk.')
                "  Copying disk " + $foldername | Out-Host
                "   src len " + $sourceLength | Out-Host
                $speak.Speak('checking if destination path is already used ' + $volumename.ToString())
                $validpath=(testpath "$destpath\$foldername")

                copy-item "$dvdpath\VIDEO_TS\" "$validpath\VIDEO_TS\" -Recurse -Force 
            #get destination length 
                $destChilds = Get-ChildItem "$validpath\VIDEO_TS\" | Measure-Object -Property length -Sum 
                $speak.Speak('Checking destination integrity')
                $destLength = $destChilds.Sum
                "   dst len " + $destLength | Out-Host

            if ($sourceLength -ne $destLength){
                "   Error! " | Out-Host
                $speak.Speak('ERROR ERROR ERROR ERROR ERROR')

                    "ok" | out-host

            start-sleep -Seconds 10

            $speak.Speak('Hello   the D--V-D has finished!')
            $speak.Speak('Hello   the D-V-D has finished!')

            "  Finished " + $foldername | Out-Host

            # Eject CD

            start-sleep -Seconds 10

            #end of while

    #end of function

# MAIN ----------------------------------------------------------------------

"--------------- starting DVD Auto Copy" | Out-Host


while ($true){




Open Sourcing – all my code is belong to you!

Version 0.1

SYNOPSIS: In the spirit of sharing that enabled me to learn how to code, I am now open sourcing all of my code, that it may be of some use to others.  Enjoy!

TO DO: I'd like to get around to describing all of the problems each solution solved for, but for now its just a minimalist dump.


I first learned to code in ZX Spectrum BASIC at around the age of 10.  It was 1986.  More accurately, I learned to type and eventually understand the code from various ‘program your own games’ books and ‘Sinclair User’ magazine articles.

It was a little later that I actually achieved an understanding of the primitive building blocks of the code.

Quite possibly my young brain was prepped for the logic and structure of programming through my obsession with the essentially ‘if-then-else’ based Fighting Fantasy series of ‘choose your own adventure’ game books.

‘The Warlock of Firetop Mountain’ was the first I remember owning, and I believe the first in the series. They were awesome.  My very early unguided ventures into coding my own programs were literally codifications of these simple narratives.

I, like many people, have learned a lot of what I know about coding from commercially driven ‘pay for knowledge’ sources such as at university, training courses, and books. However, I am just old enough to claim some memory of the relatively early years of open source code sharing.

So, like everybody, I have also learned a stack from the open source information made available by the generosity, or naivety, nevertheless sheer enjoyment of sharing through magazines, blogs,  websites, friends and various other mediums.

So, I have decided to dig up and throw into the pot of shared online code, a bunch of my own work; better or poorer to be debated, but certainly useful.

Note: I don’t claim that any of this code still works, nor either that that which does, is optimal for the task.  Much of it is old, some of it is new, the context and constraints of the environment it was written for are here absent, but it all served a purpose.


[ 6 x 9 = 42 ] How six times nine equals forty two, and why it is the most powerful formula in the universe!

Six times nine equals forty two.  This is a cultural and mathematical certainty.

But it doesn’t … does it?  Yes it does!

Understanding how this apparent aberration of mathematics works can unlock the secrets of life, work, relationships, business, and, you know, everything.

So don’t panic!  Grab a cup of tea, find your towel, and prepare to disrupt your universe.

Why forty two?

Forty two is a very important number.

In fact, it’s not just a number.  It is the answer.  The ultimate answer.  The answer to life the universe and everything.

Continue reading [ 6 x 9 = 42 ] How six times nine equals forty two, and why it is the most powerful formula in the universe!

PowerShell Persistent eMail Sender – PixSeeMailR

The Problem

I have a number of scheduled PowerShell scripts that perform various system health checks.  If any of them detect something is amiss they send an alert email to myself and my support group using the send-mailmessage cmdlet.

However if the mail server was inaccessible when attempting to send-mailmessage, the command and resulting alert messages would fail.  This was a probable scenario given that the purpose of the error checking scripts is to detect problems including the inaccessibility of network resources.

So I wanted to easily add a level of persistence to the error message emails without having to require the individual health check scripts run continuously.

My solution was to have the checking scrpits place email details into an xml file, and have a batch emailing script pickup and send those messages when it could connect to the mail server.


I considered installing a local SMTP mail server such as hMail, and just let it relay through the corporate SMTP server.  However;

  1. I didn’t want to introduce new software to the server, and even though hMail is relatively simple, even it is seems like overkill for the requirement.
  2. I didn’t think the corporate security guys would appreciate me installing an SMTP server on the network.
  3. Most importantly, I felt like stretching my brain doing some PowerShell problem solving.

So I thought a PowerShell only solution would  be nice.

The Solution

Rather than have all my PowerShell scripts handle emailing I decided to create a simple email handling script I called PixSeeMailR.  (The name makes sense to me in the suite of scripts for which it is used.  See my article PixSeeLog: Error detection through image pixel inspection.)

The various other monitoring scripts create email messages (PowerShell objects that contain all the relevant message parameters) and deposit them into an “Out-Mail” folder as an XML file.

Messages in the Out-Mail folder are processed by PixSeeMailR periodically and moved to a “Sent-Mail” folder when they have been processed.  If the message processing fails for any number of reasons, the messages remain in the Out-Mail folder to be processed in the next period.

This solution design uses a scheduled PowerShell script to check for messages.  I also did consider using the System.IO.FileSystemWatcher to trigger the processing of messages as soon as they were deposited into the Out-Mail folder.  That would require the script to run persistently however and for this exercise I wanted to rely on scheduled script execution.

The following diagram shows how the process will work.

PowerShell Persistent Email Sender

Step 1 – Create Message

The first part of the solution is to create messages.

The following code creates message files by storing all the email message parameters into a PowerShell object and exporting that object to an XML file with Export-Clixml.

I use a message naming convention that guarantees (probably) unique message names.

This Create Message code can be added to any PowerShell script and activated according to whatever criteria is relevant.

$messageObject = @{
    MailTo = "chris@emaildomain.com", "somebody@emaildomain.com"
    MailFrom = "Testing Script <service_account@maildomain.com>"
    MailSubject = "Error Detected"
    MailPriority = "High"
    MailBody="Hello this is a test message"
    MailCreateTime = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
    MessageID= (Get-Date -Format "yyyy_MM_dd_HH_mm_ss_fff")+"_servicename_" + $env:COMPUTERNAME +".xml"
Export-Clixml ("D:\PixSeeMailR\Out\"+$messageObject.MessageID) -InputObject $messageObject

Step 2 – Check For and Send Messages

The following code is designed to be run as a scheduled task.  I run it every 60 seconds, but you could run it at whatever frequency you like.

  1. The code performs the following steps:
  2. Check if there are any emails to send
  3. Check if the mail server is accessible
  4. Attempt to send each email
  5. Move sent emails to the ‘Sent’ folder
  6. Any messages that were unable to send successfully remain in the ‘Out’ folder and have their Attempts attribute incremented.


PixSeeMailR by Chris 
October 2016

# == Start Diagnostic Timer
$global:currentLog = ""

$SWScriptTime = [Diagnostics.Stopwatch]::StartNew()

#======================== FUNCTIONS

 # ====================== Format Date Function

 function GetDateFormattedForPaths(){
 Return (Get-Date -Format "yyyy-MM-dd HHmmss")

 function GetDateFormatted(){
 Return (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
 function GetDateOnlyFormatted(){
 Return (Get-Date -Format "yyyy-MM-dd")

 # ====================== Logging Function
 function LogThis($logthisstring){
 $global:currentLog = $global:currentLog + "`r`n"+ ($logthisstring )
 $logEntry = (GetDateFormatted) + " ~ " + $logthisstring
 $logEntry | Out-File $logOutputPath -Append
 $logEntry | Out-host
 # ====================== Log Cleaning Function 
 function CleanLogs(){
 $logCleanLimit = -90
 $limit = (Get-Date).AddDays($logCleanLimit)
 $path = $logPath
 logthis("Cleaning Log Files older than " + $logCleanLimit + " Days")
 # Delete files older than the $limit.
 Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force

 # ====================== Exit Block

 $ExitNow = {
 # find me a line Morpheus 
 logthis("EXIT NOW! Script complete in " + $SWScriptTime.Elapsed + " ... bravely bravely run away ...")
 $global:currentLog | Out-File $currentLogPath -Force

#======================== VARIABLES

 $mailserver = "smpt.mail.com" #use your own SMTP server 
 $mailoutpath = "D:\PixSeeMailR\out\"
 $mailsentpath = "D:\PixSeeMailR\sent\"
 $logdate = getdateonlyformatted
 $logPath = "D:\PixSeeMailR\logs\"
 $logOutputPath = (("D:\PixSeeMailR\logs\") + $logdate + ("-MessageRelayLog.txt"))
 $currentLogPath = ("D:\PixSeeMailR\logs\MessageRelay_current.txt")

#======================== BEGIN

#Populate mails object

 $mails = Get-ChildItem $mailoutpath -filter "*.xml"

# check if there are any emails to send
 logthis("Checking for messages at; " + $mailoutpath )
 if($mails.count -eq 0){ 
 # There are no messages to send.
 logthis(" No Mail to Send - Ending")
 # There are messages - proceed!
 logthis(" " + $mails.Count + " Mails exist.") 
 # Check if mail server is available
 logthis("Checking mail SMTP Relay server at: " + $mailserver)
 if((Test-NetConnection $mailserver -port 25).tcptestsucceeded -eq $true){
 # Mail Server is Available
 logthis(" Mail Server is available at: " + $mailserver) 
 # Process messages 
 $Message = 1
 foreach($mail in $mails){
 Logthis(" Processing Message " + $Message + " " + $mail.fullname)

 $messageObject = Import-Clixml $mail.FullName
 ("@" + $messageobject.MailAttempt + ": " + $messageobject.MailSubject)

 # $messageobject.MailTo
 # $messageobject.MailFrom
 # $messageobject.MailSubject
 # $messageobject.MailBody
 if ($messageObject.MailAttachments -eq ""){
 logthis (" process mail without attachments")
 send-mailmessage -to $messageobject.MailTo -from $messageobject.MailFrom -subject ("@" + $messageobject.MailAttempt + ": " + $messageobject.MailSubject) -body $messageobject.MailBody -priority $messageobject.MailPriority -SmtpServer $mailServer -erroraction Stop #-Credential $cred
 logthis (" mail has attachments")
 send-mailmessage -to $messageobject.MailTo -from $messageobject.MailFrom -subject ("@" + $messageobject.MailAttempt + ": " + $messageobject.MailSubject) -body $messageobject.MailBody -priority $messageobject.MailPriority -Attachments $messageObject.MailAttachments -SmtpServer $mailServer -erroraction Stop #-Credential $cred
 logthis(" Moving message from: " + $mail.FullName)
 logthis(" Moving message to: " + ($mailsentpath + $mail.Name))
 move-item $mail.FullName ($mailsentpath + $mail.Name)
 Logthis(" ------------------------------------ end")

 logthis ("ERROR - an error has occurred sending message: " + $mail.FullName)
 $errorcount = $error.count -1
 #logthis ($error.item($errorcount))
 logthis (" Incrementing send attempt to " + (1+($messageobject.MailAttempt)))
 $messageobject.mailattempt = 1+($messageobject.MailAttempt)
 Export-Clixml $mail.FullName -InputObject $messageObject
 # else mail server is not available - log and exit
 logthis("ERROR - Mail server is unavailable at: " + $mailserver + " - Exiting!") 




The solution presented here relies on two success testing methods.

1 – A simple TestNet-Connection against the target SMTP server.  If the server is not available the messages don’t attempt to send.

2 – The success or failure of the Send-MailMessage cmdlet.  If the cmdlet returns any error, the message is flagged for retry.

This approach doesn’t guarantee that the messages will reach the intended recipient, only that the SMTP server accepted the messages without error.

Future Upgrade Ideas

Next, I would like to implement the following upgrades;

  1. Turn the PixSeeMailR into a Windows service.
  2. Consider using System.IO.FileSystemWatcher to trigger mail sends immediately.


PixSeeMailR has been running on several of my servers for the past few days and seems to be working well.  I have scheduled a daily test message drop for 10 am every day and so far they have been processed as expected.

The average run time when there are no messages is 00.025 seconds.