Send Text and Email to User from Task Sequence

Bonus: Learn to use Gmails SMTP service.

Updated 1/31/2019 – Updated Script to work independently of XAML Form, you can use the last script to send text / email by running the script and using arguments: SendTextEmail.ps1 -UserEmail gwblok@garytown.com -UserCell 6512015555 -UserCellProvider Verizon

Updated 3/6/2018 – added auto close feature, so if user ignores the window, it will auto close allowing the TS to continue. (Updated the 3rd Form image below to reflect the change)

Or if some of you awesome PowerShell guys want to make my code pretty and add that feature…  🙂

As I try to improve the user experience for in-place upgrades, notifications came to mind, and how to incorporate that into the process.  Using the TS variable _SMSTSUserStarted = true, I launch a powershell driven “front end” to collect the user’s email, cell number & provider, then use that to notify user when process is complete.

 

Using @FoxDeploy’s guide and a little help over twitter, I was able to create a form to collection the information.

The form launches, to ask for the email, then check a box, if you check the box, it un-hides the rest of the form required for the text, and also “greys-out” the “OK” button, until the required information is collected.

 

Once the user inserts their Cell Number & selects a
radio button, the Ok button lights back up, and they can choose OK.  The Skip button just closes the form allowing the TS to continue with no data collected.

Once you click OK, the Data goes into the registry & into TS Variables.  I add it to the registry because I want it there for the next upgrade.  When the form loads, it will look for those keys, if they are there, it will auto populate, allowing the user to then click OK, or modify first, and then continue.

Code for Form:

# Most of this is borrowed straight from FoxDeploy.com
# https://foxdeploy.com/2015/04/10/part-i-creating-powershell-guis-in-minutes-using-visual-studio-a-new-hope/
# And I what I couldn't figure out, he typically helped me! @FoxDeploy, this wouldn't have been possible without him.

$TSProgressUI = new-object -comobject Microsoft.SMS.TSProgressUI
$TSProgressUI.CloseProgressDialog()


#Grab Registry Values if they are there, we can use this to prepopulate the Form from the previous upgrade.
$UserCell = (Get-ItemProperty "HKLM:\SOFTWARE\GaryTown\UserInfo").UserCell
$UserCellProvider = (Get-ItemProperty "HKLM:\SOFTWARE\GaryTown\UserInfo").UserCellProvider
$UserEmail = (Get-ItemProperty "HKLM:\SOFTWARE\GaryTown\UserInfo").UserEmail


#Place to write information to Registery - For future deployments, can grab and ask user if this is correct... still need to develop that idea.. just laying the groundwork for the future
#This also works well if you want to run this outside of the TS, then you can have the TS grab those keys to use.
#User's Email Address
#User's Cell Number
#User's Carrier (Required to send the text)
$registryPath = "HKLM:\Software\GaryTown\UserInfo"

#Check if running in TS
try
{
    $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
    #$tsenv.CloseProgressDialog()
}
catch
{
	Write-Verbose "Not running in a task sequence."
}




#ERASE ALL THIS AND PUT XAML BELOW between the @" "@ - XAML Code Generated from Vistual Studio Community Ed.
$inputXML = @"
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="Optional User Notifications" Height="350" Width="525">
    <Grid x:Name="UserDialog" Margin="0,0,0,-0.5">
        <Grid.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="Black" Offset="1"/>
                <GradientStop Color="White"/>
            </LinearGradientBrush>
        </Grid.Background>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="27*"/>
            <ColumnDefinition Width="103*"/>
        </Grid.ColumnDefinitions>
        <Label x:Name="LabelInfo" Content="If you would like to receive an email or test message when the &#xD;&#xA;upgrade is complete, please fill in the form and click OK." HorizontalAlignment="Left" Height="52" Margin="23,10,0,0" VerticalAlignment="Top" Width="487" Grid.ColumnSpan="2" FontSize="16"/>
        <TextBox x:Name="TBEmail" HorizontalAlignment="Left" Height="28" TextWrapping="Wrap" VerticalAlignment="Top" Width="233" Margin="105,63,0,0" Grid.ColumnSpan="2"/>
        <Label x:Name="LabelEmail1" Content="Email:" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="23,64,0,0" Width="77" FontWeight="Bold" Height="28"/>
        <TextBox x:Name="TBCell" HorizontalAlignment="Left" Height="26" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" Margin="209,119,0,0" Grid.Column="1" Visibility="Hidden"/>
        <Label x:Name="LabelCell" Content="Cell:  (1112223333, no spaces or other characters) " HorizontalAlignment="Left" VerticalAlignment="Top" Margin="23,118,0,0" Grid.ColumnSpan="2" FontWeight="Bold" Visibility="Hidden"/>
        <RadioButton x:Name="RBAlltel" Content="AllTel" HorizontalAlignment="Left" Margin="113.552,161,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden"/>
        <RadioButton x:Name="RBATT" Content="AT&amp;T" HorizontalAlignment="Left" Margin="113.552,181,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden" />
        <RadioButton x:Name="RBVirgin" Content="Virgin Mobile" HorizontalAlignment="Left" Margin="205,161,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden"/>
        <RadioButton x:Name="RBTMobile" Content="T-Mobile" HorizontalAlignment="Left" Margin="114,201,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden" />
        <RadioButton x:Name="RBVerizon" Content="Verizon" HorizontalAlignment="Left" Margin="114,220,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden" />
        <RadioButton x:Name="RBRepublic" Content="Republic Wireless" HorizontalAlignment="Left" Margin="205,181,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden"/>
        <RadioButton x:Name="RBUSCellular" Content="U.S. Cellular" HorizontalAlignment="Left" Margin="205,202,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden"/>
        <RadioButton x:Name="RBSprint" Content="Sprint &amp; Boost Mobile" HorizontalAlignment="Left" Margin="205,220,0,0" VerticalAlignment="Top" Grid.Column="1" FontWeight="Bold" Foreground="#FFD5D5D5"  Visibility="Hidden"/>
        <Label x:Name="LabelCarrier" Content="Please select your Cell Carrier:" HorizontalAlignment="Left" Height="35" Margin="23,155,0,0" VerticalAlignment="Top" Width="184" Grid.ColumnSpan="2" FontWeight="Bold" Foreground="#FFD5D5D5" Visibility="Hidden"/>
        <Button x:Name="ButtonOK" Content="OK" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="195.552,281,0,0" Grid.Column="1"/>
        <Button x:Name="ButtonSkip" Content="Skip" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="302.552,281,0,0" Grid.Column="1"/>
        <CheckBox x:Name="CBSMS" Content="Check if you would like  to receive a text message" HorizontalAlignment="Left" Margin="28,93,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Width="310"/>
        <Label x:Name="LabelCellError" Content="Please confirm your Cell &#xD;&#xA;number, it should only be &#xD;&#xA;10 numbers long" Grid.Column="1" HorizontalAlignment="Left" Margin="235,60,0,0" VerticalAlignment="Top" Height="81" Width="171" Visibility="Hidden"/>
        <Label x:Name="LabelGTown" Grid.ColumnSpan="2" Content="GARYTOWN.COM" HorizontalAlignment="Left" Height="41" Margin="29,271,0,0" VerticalAlignment="Top" Width="207" FontWeight="Bold" FontSize="20" Foreground="#FF6B5C5C"/>
        <TextBox x:Name="TBTimeBox" HorizontalAlignment="Left" Height="23" Margin="20,248,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="60" Grid.Column="1"/>
        <Label x:Name="LabelAutoClose" Content="Auto Close in:" VerticalAlignment="Top" Margin="37,245,354,0" FontWeight="Bold" Height="28" Grid.ColumnSpan="2" Foreground="#FF6B5C5C"/>


    </Grid>
</Window>
"@       
 
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N'  -replace '^<Win.*', '<Window'
 
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
#Read XAML
 
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
try{$Form=[Windows.Markup.XamlReader]::Load( $reader )}
catch{Write-Warning "Unable to parse XML, with error: $($Error[0])`n Ensure that there are NO SelectionChanged properties (PowerShell cannot process them)"
    throw}
 
#===========================================================================
# Load XAML Objects In PowerShell
#===========================================================================
  
$xaml.SelectNodes("//*[@Name]") | %{"trying item $($_.Name)";
    try {Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -ErrorAction Stop}
    catch{throw}
    }
Function Get-FormVariables{
if ($global:ReadmeDisplay -ne $true){Write-host "If you need to reference this display again, run Get-FormVariables" -ForegroundColor Yellow;$global:ReadmeDisplay=$true}
write-host "Found the following interactable elements from our form" -ForegroundColor Cyan
get-variable WPF*
}
 
Get-FormVariables

#AutoFills Text Box & Radio Buttons if they were filled out before.
$WPFTBCell.Text = $UserCell
$WPFTBEmail.Text = $UserEmail

if ($UserCellProvider -eq 'alltel'){$WPFRBAlltel.IsChecked = $true}`
elseif ($UserCellProvider-eq 'ATT'){$WPFRBATT.IsChecked = $true}`
elseif ($UserCellProvider -eq 'Sprint'){$WPFRBSprint.IsChecked = $true}`
elseif ($UserCellProvider -eq 'TMobile'){$WPFRBTMobile.IsChecked = $true}`
elseif ($UserCellProvider -eq 'Verizon'){$WPFRBVerizon.IsChecked = $true}`
elseif ($UserCellProvider -eq 'Virgin'){$WPFRBVirgin.IsChecked = $true}`
elseif ($UserCellProvider -eq 'Republic'){$WPFRBRepublic.IsChecked = $true}`
elseif ($UserCellProvider -eq 'Cellular'){$WPFRBUSCellular.IsChecked = $true}

#===========================================================================
# Actually make the objects work
#===========================================================================


#gather all of the settings the user specifies, needed to splat to the New-ADUser Cmd later
function Get-FormFields {

   $CellProvider = if ($WPFRBAlltel.IsChecked -eq $true){'alltel'}`
                elseif ($WPFRBATT.IsChecked -eq $true){'ATT'}`
                elseif ($WPFRBSprint.IsChecked -eq $true){'Sprint'}`
                elseif ($WPFRBTMobile.IsChecked -eq $true){'TMobile'}`
                elseif ($WPFRBVirgin.IsChecked -eq $true){'Virgin'}`
                elseif ($WPFRBRepublic.IsChecked -eq $true){'Republic'}`
                elseif ($WPFRBUSCellular.IsChecked -eq $true){'Cellular'}`
                elseif ($WPFRBVerizon.IsChecked -eq $true){'Verizon'}
    return $CellProvider
     
          
          }

$WPFTBCell.Add_TextChanged({
    If (($WPFTBCell.Text.Length -ge 11)){
      $WPFLabelCellError.Visibility = 'Visible'
    }else
    {
    $WPFLabelCellError.Visibility = 'Hidden'
    }
}) 

#When you Check the Box to receive SMS text, it unhides several boxes and DISABLES the OK button (until a radio option is clicked)
$WPFCBSMS.Add_Checked({
    $WPFTBCell.Visibility = 'Visible'
    $WPFLabelCell.Visibility = 'Visible'
    $WPFRBAlltel.Visibility = 'Visible'
    $WPFRBATT.Visibility = 'Visible'
    $WPFRBSprint.Visibility = 'Visible'
    $WPFRBTMobile.Visibility = 'Visible'
    $WPFRBVirgin.Visibility = 'Visible'
    $WPFRBVerizon.Visibility = 'Visible'
    $WPFRBRepublic.Visibility = 'Visible'
    $WPFRBUSCellular.Visibility = 'Visible'
    $WPFLabelCarrier.Visibility = 'Visible'
    if ($UserCellProvider -ne $null){$WPFbuttonOK.IsEnabled = $true}
    else {$WPFbuttonOK.IsEnabled = $false}

    })
#This will hide the items again if you uncheck the box
$WPFCBSMS.Add_UnChecked({
    $WPFTBCell.Visibility = 'Hidden'
    $WPFLabelCell.Visibility = 'Hidden'
    $WPFRBAlltel.Visibility = 'Hidden'
    $WPFRBATT.Visibility = 'Hidden'
    $WPFRBSprint.Visibility = 'Hidden'
    $WPFRBTMobile.Visibility = 'Hidden'
    $WPFRBVerizon.Visibility = 'Hidden'
    $WPFRBVirgin.Visibility = 'Hidden'
    $WPFRBRepublic.Visibility = 'Hidden'
    $WPFRBUSCellular.Visibility = 'Hidden'
    $WPFLabelCarrier.Visibility = 'Hidden'
    })

#This enables the OK button if a radio button is choosen (Select a Carrier)
$WPFRBAlltel.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBATT.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBSprint.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBTMobile.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBVerizon.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBVirgin.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBRepublic.Add_Click({$WPFbuttonOK.IsEnabled = $true })
$WPFRBUSCellular.Add_Click({$WPFbuttonOK.IsEnabled = $true })


$WPFbuttonOK.Add_Click({
$CellProvider = Get-FormFields

if ($tsenv)
{
$tsenv.Value('TSUserEmail') = $WPFTBEmail.Text
$tsenv.Value('TSUserCell') = $WPFTBCell.Text
$tsenv.Value('TSUserCellProvider') = $CellProvider
}

if ( -not ( test-path $registryPath ) ) {new-item -ItemType directory -path $registryPath -force -erroraction SilentlyContinue | out-null}
New-ItemProperty -Path $registryPath -Name "UserEmail" -PropertyType String -Value $WPFTBEmail.Text -Force
New-ItemProperty -Path $registryPath -Name "UserCell" -PropertyType String -Value $WPFTBCell.Text -Force
New-ItemProperty -Path $registryPath -Name "UserCellProvider" -PropertyType String -Value $CellProvider -Force


start-sleep -Milliseconds 840

$form.Close()
})

$WPFButtonSkip.Add_Click({
start-sleep -Milliseconds 840
$form.Close()
})

#Timer code borrowed from: https://social.microsoft.com/Forums/en-US/e2dfa35f-607e-4749-8ac7-776001b2cbe8/powershell-countdown-timer-wpf-appearance-and-trouble-updating-text?forum=Offtopic
#Event handlers            
$form.Add_SourceInitialized({
    # Before the window's even displayed ...           
    # We'll create a timer           
    $script:seconds =([timespan]0).Add('0:5')  # 5 minutes
    $script:timer = new-object System.Windows.Threading.DispatcherTimer
    # Which fire 1 time each second
    $timer.Interval = [TimeSpan]'0:0:1.0'
    # And will invoke the $updateBlock         
    $timer.Add_Tick.Invoke($UpDateBlock)
    # Now start the timer running           
    $timer.Start()
    if ($timer.IsEnabled -eq $false) {
        write-warning "Timer didn't start"
    }
})

$UpDateBlock = ({
    $script:seconds= $script:seconds.Subtract('0:0:1')
    $WPFTBTimeBox.Text=$seconds.ToString('mm\:ss')
    if($seconds -eq 0) {  $form.Close()  }
}) 
#Sample entry of how to add data to a field
 
#$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"})
 
#===========================================================================
# Shows the form
#===========================================================================
write-host "To show the form, run the following" -ForegroundColor Cyan
$Form.ShowDialog() | out-null

 

TS Step for Form:
image

Code from Command Line: *NOTE, requires ServiceUI.exe in the same package as the scripts:

ServiceUI.exe -process:TSProgressUI.exe %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File XAML.ps1

image

At the end of the TS, there is a last step that runs another script that will grab the variables and send the email and text.  Currently I’m using Gmail’s SMTP service to do it, I created a new gmail account and set the required changes needed to use it to relay SMTP email. Instructions HERE – Follow the instructions for use Gmail SMTP Server, which requires you to first set “Less Secure Apps” is enabled for the account. The username & password are in clear text in my script, feel free to modify it to use a more secure method, like a password file, or use an internal SMTP server that doesn’t require authentication.

Why the radio buttons for the carrier?  Most carriers have a way to send text using an email address.  I use this feature to append the email address suffix onto the cell number and send the email to that cell phone’s SMS email address, so it comes in as a text.  If your carrier isn’t listed, you can probably look it up on their website.  Example, I use Boost Mobile, which is on the Sprint Network, in my testing, using my cell number and the Sprint’s email, it would send me the text.  You can probably just google it too..
image

Code for Script:

#Script cobbled together by @gwblok - garytown.com
#Gmail Send Mail Script orginally borrowed from: http://petermorrissey.blogspot.ro/2013/01/sending-smtp-emails-with-powershell.html
#To use this to send a text message was of my own design.
[CmdletBinding()]
Param (
    #ForceTSReset will Trigger Reset-TaskSequence Function at end of TS if True
    [Parameter(Mandatory=$false)][string] $UserEmail,
    [Parameter(Mandatory=$false)][string] $UserCell,
    [Parameter(Mandatory=$false)][string] $UserCellProvider
    )


#Settings for Google
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "username@gmail.com"
$Password = "userpassword"

#Connect to Task Sequence Environment
try
{
    $tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
}
catch
{
	Write-Verbose "Not running in a task sequence."
}
if ($tsenv)
    {
    if ($tsenv.Value("TSUserEmail")) {$UserEmail = $tsenv.Value("TSUserEmail")}
    if ($tsenv.Value("TSUserCell")) {$UserCell = $tsenv.Value("TSUserCell")}
    if ($tsenv.Value("TSUserCellProvider")) {$UserCellProvider = $tsenv.Value("TSUserCellProvider")}
    if ($tsenv.Value("_SMSTSPackageName")) {$TSname = $tsenv.Value("_SMSTSPackageName")}
    }

if ($UserCellProvider) 
    {
    if ($UserCellProvider -eq 'AllTel'){$UserCellProvider = "@message.alltel.com"}
    elseif ($UserCellProvider -eq 'ATT'){$UserCellProvider = "@text.att.net"}
    elseif ($UserCellProvider -eq 'Sprint'){$UserCellProvider = "@messaging.sprintpcs.com"}
    elseif ($UserCellProvider -eq 'TMobile'){$UserCellProvider = "@tmomail.net"}
    elseif ($UserCellProvider -eq 'Verizon'){$UserCellProvider = "@vtext.com"}
    elseif ($UserCellProvider -eq 'Virgin'){$UserCellProvider = "@vmobl.com"}
    elseif ($UserCellProvider -eq 'Republic'){$UserCellProvider = "@text.republicwireless.com"}
    elseif ($UserCellProvider -eq 'Cellular'){$UserCellProvider = "@email.uscc.net"}
    }

#Email & Phone Numbers go Here
$to = $UserEmail
if ($UserCell){$cc = "$UserCell$UserCellProvider"}

#Customize this Part for your Subject & Body.  Keep it short if you plan to use SMS texts.
$subject = "TS Notification: $TSname Complete"
$body = "The Task Sequence $TSname on $env:computername Has Been Completed, you can now log into the PC"

$message = New-Object System.Net.Mail.MailMessage
$message.subject = $subject
$message.body = $body
$message.to.add($to)
if ($UserCell){$message.cc.add($cc)}
$message.from = $username

$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
write-host "Mail Sent"

 

Step in TS:
image

Feel free to skip the user interaction part, and just hardcode the script to send a text or email to a specific person for every upgrade / OSD deployment, like your boss, so you can prove how often you upgrade a machine successfully, I’m sure the sentiment will be of Awe, and not annoyance.

 

As always, please leave a comment or hit me up on twitter.  I only tested sending text messages to Boost Mobile & Verizon, but I ASSUME rest of them will work.  If you find any problem, please leave a comment to help others.

 

PS.. please forgive my powershell code, I’m still learning, and most of it is thanks to googling and trial and error.

4 thoughts on “Send Text and Email to User from Task Sequence”

  1. Just what I was looking for!

    For my purpose at an MSP, I needed a script to run in WinPE (using MDT) during the question file portion, ask for the technician’s e-mail, and/or phone number, to let them know when a deployment was complete. I made a few modifications to send the e-mail to our Exchange server. To save the variables in WinPE, you do need to dump the variables to the variables.dat file, which is detailed here:
    https://deploymentresearch.com/Research/Post/541/Quick-Fix-MDT-Environment-Variables-set-in-PowerShell-are-not-flushed-to-Variables-dat
    Other than that, great script!

    Reply
  2. Hi,
    We have a been using a script which does a different role to this, as it offers a menu to install a chosen language pack, unfortunately, since upgrading to SCCM 1806, the menu has stopped appearing? It looks like it could be down to the xmlnodereader not loading, any ideas to help us try an fix the issue?
    Thanks

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.