Invoke-SHDMoveFiles

Invoke-SHDMoveFiles

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.

Invoke File Service Integrity Check

Invoke File Service Integrity Check

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.

Password Notifications

Password Notifications

I wanted to share a little script that has changed the world at my current company. It’s a simple password email notification script. What it does is grabs the users’ whos passwords about to expire. Then it emails that user with instructions on how to reset their password. Then it sends an email to the user’s manager with a list of when the user’s password will expire. Then finally it sends an email to another person with a complete list of all expiring users and when they will expire. After the user hits zero, the script no longer cares about that person. This is to prevent lots of headaches in the long run with on leave and whatnots. The best thing to do is to set up a scheduled task on a server with rsats. The computer will also need a relay setup on the exchange server.

The Script

param (
    [parameter(Helpmessage = "Return Email", Mandatory = $true)][string]$ReturnEmail,
    [parameter(Helpmessage = "Email Sent to IT", Mandatory = $true)][string]$ITEmail,
    [parameter(Helpmessage = "Mail Server", Mandatory = $true)][string]$MailServer,
    [parameter(Helpmessage = "Employee Organizational Unit", Mandatory = $true)][string]$EmployeeOU,
    [parameter(Helpmessage = "Training Link", Mandatory = $true)][string]$TrainingLink,
    [parameter(Helpmessage = "Training Link", Mandatory = $true)][string]$DaysWithinExpiration
)
$MaxPwdAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$expiredDate = (Get-Date).addDays(-$MaxPwdAge)
$emailDate = (Get-Date).addDays( - ($MaxPwdAge - $DaysWithinExpiration))
$ExpiredUsers = Get-ADUser -Filter { (PasswordLastSet -lt $emailDate) -and (PasswordLastSet -gt $expiredDate) -and (PasswordNeverExpires -eq $false) -and (Enabled -eq $true) } -Properties DisplayName, PasswordNeverExpires, Manager, PasswordLastSet, Mail, "msDS-UserPasswordExpiryTimeComputed" -SearchBase "$EmployeeOU" | Select-Object DisplayName, samaccountname, manager, PasswordLastSet, @{name = "DaysUntilExpired"; Expression = { $_.PasswordLastSet - $ExpiredDate | Select-Object -ExpandProperty Days } }, @{name = "EmailAddress"; Expression = { $_.mail } }, @{Name = "ExpiryDate"; Expression = { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") } } | Sort-Object PasswordLastSet
$ExpiredUsers
foreach ($ExpiredUser in $ExpiredUsers) {
    $ReturnHTML = @" 
<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;
}
h2 {
    text-align: center;
    color:#9EA2A2;
    Font-size: 20pt;
}
h3 {
    text-align: center;
    color:#211b1c;
    Font-size: 15pt;
}
h4 {
    text-align: center;
    color:#242526;
    Font-size: 15pt;
}
a:link {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
a:visited {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
</style>
</head>
<body>
Dear $($ExpiredUser.DisplayName),<br><br>
Your password was last set on <i>$($ExpiredUser.PasswordLastSet)</i>. This means your password will expire in <b>$($ExpiredUser.DaysUntilExpired) Days</b>. Please reset your password before <b>$($ExpiredUser.ExpiryDate)</b><br>
<ol>
<li>Find a computer connected to the network</li>
<li>if the computer is logged in, on your keyboard press, <b>ctrl+alt+del</b> at the same time. </li>
<li>Select <b>Change Password</b></li>
<li>replace the username section with <b>$($ExpiredUser.samaccountname)</b>.</li>
<li>Enter your old password in the old password location.</li>
<li>Enter a new password in the new password location.</li>
<li>Confirm your new password in the confirm password location</li>
<li>Click Enter</li>
</ol>
<br>
<hr>
<br>
For more Information on how to change your password Please watch these videos.<br>
<a href='$TrainingLink'>How to change your Password.</a><br><br>

Thank you<br>
IT Department
</body> 
</html> 
"@  
    Send-MailMessage -To $($ExpiredUser.EmailAddress) -From "$ReturnEmail" -Subject "$($ExpiredUser.DisplayName) Password Notification" -BodyAsHtml $ReturnHTML -SmtpServer $MailServer
}
$Managers = $ExpiredUsers | Select-Object -ExpandProperty manager -Unique
foreach ($Manager in $Managers) {
    $ReportingtoHTML = $ExpiredUsers | Where-Object { $_.manager -eq $Manager } | Select-Object -Property DisplayName, @{name = "EmailAddress"; Expression = { $_.mail } }, @{label = "DaysUntilExpired"; expression = { "$($_.DaysUntilExpired) Days" } }, ExpiryDate | ConvertTo-Html -Fragment -As Table
    $Manager = Get-ADUser $Manager -Properties DisplayName, PasswordNeverExpires, Manager, PasswordLastSet, "msDS-UserPasswordExpiryTimeComputed" | Select-Object DisplayName, samaccountname, mail, manager, PasswordLastSet, @{name = "DaysUntilExpired"; Expression = { $_.PasswordLastSet - $ExpiredDate | Select-Object -ExpandProperty Days } }, @{name = "EmailAddress"; Expression = { $_.mail } }, @{Name = "ExpiryDate"; Expression = { [datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed") } } | Sort-Object PasswordLastSet
    $ManagerReturnHTML = @" 
<html> 
<head>
<style>
h1 {
    text-align: center;
    color:#C8102E;
    Font-size: 34pt;
    font-family: Verdana, Arial;
}
h2 {
    text-align: center;
    color:#9EA2A2;
    Font-size: 20pt;
}
h3 {
    text-align: center;
    color:#211b1c;
    Font-size: 15pt;
}
h4 {
    text-align: center;
    color:#242526;
    Font-size: 15pt;
}
a:link {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
a:visited {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
body {
width:75%;
background-color:#ffffff;}
table {border-collapse: collapse; border: 1px solid rgb(45,41,38); text-align: Center;}
th {background-color: #f7a1af; text-align: Center;}
tr:nth-child(even) {background-color: #f2f2f2;}
tr {text-align: Center;}
td {border:1px solid rgb(45,41,38);text-align: Center;}
</style>
</head>
<body style="font-family:verdana;font-size:13"> 
Dear $($Manager.DisplayName),<br><br>
Here is a list of all employees that are reporting to you whose passwords are to expire within the next 14 days. Please have these employees reset their passwords <b>before</b> their expirydate. Please note that we have also sent an email to the employee with the instructions on how to reset their password.<br>
<br>
$ReportingtoHTML
<br>
The below videos have been sent to these employees. If the employee needs help, refer them to these videos or instruct them how to reset their passwords.<br>
<a href='$TrainingLink'>How to change your Password.</a><br><br>
<br>

Thank you<br>
IT Department
</body> 
</html> 
"@  
    Send-MailMessage -To $($Manager.EmailAddress) -From "$ReturnEmail" -Subject "User Password Notification" -BodyAsHtml $ManagerReturnHTML -SmtpServer $MailServer
}
$ITReturnHTML = $ExpiredUsers | Select-Object @{label = "Name"; expression = { $_.Displayname } }, @{Label = "UserName"; expression = { $_.samaccountname } }, EmailAddress, @{label = "Manager"; expression = { $(($_.Manager.Split(',')).split('=')[1]) } }, ExpiryDate, @{Label = "Days"; expression = { $_.DaysUntilExpired } } | ConvertTo-Html -Fragment -As Table
$ItReportReturnHTML = @" 
<html> 
<head>
<style>
h1 {
    text-align: center;
    color:#C8102E;
    Font-size: 34pt;
    font-family: Verdana, Arial;
}
h2 {
    text-align: center;
    color:#9EA2A2;
    Font-size: 20pt;
}
h3 {
    text-align: center;
    color:#211b1c;
    Font-size: 15pt;
}
h4 {
    text-align: center;
    color:#242526;
    Font-size: 15pt;
}
a:link {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
a:visited {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
body {
background-color:#ffffff;}
table {border-collapse: collapse; border: 1px solid rgb(45,41,38); text-align: Center;}
th {background-color: #54c6ff; text-align: Center;}
tr:nth-child(even) {background-color: #f2f2f2;}
tr {text-align: Center;width:20%;}
td {border:1px solid rgb(45,41,38);text-align: Center;}
</style>
</head>
<body style="font-family:verdana;font-size:13"> 
Dear IT,<br><br>
Please see the expiring/expired password list below.
<br><br>
$ITReturnHTML
<br>
The below videos have been sent to these employees. If the employee needs help, refer them to these videos or instruct them how to reset their passwords.<br>
<a href='$TrainingLink'>How to change your Password.</a><br><br>
<br>

Thank you<br>
IT Department
</body> 
</html> 
"@  
Send-MailMessage -To $ITEmail -From "$ReturnEmail" -Subject "Password Expiry Notification" -BodyAsHtml $ItReportReturnHTML -SmtpServer $MailServer
$Admins = Get-ADUser -Filter { (samaccountname -like "*_adm") -and (PasswordLastSet -lt $emailDate) -and (PasswordLastSet -gt $expiredDate) -and (PasswordNeverExpires -eq $false) -and (enabled -eq $true) } -Properties *
Foreach ($Admin in $Admins) {
    $Username = $Admin.samaccountname -replace "(_.*)", ""
    $User = Get-ADUser -Identity $Username -Properties *
    $ReturnHTML = @" 
<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;
}
h2 {
    text-align: center;
    color:#9EA2A2;
    Font-size: 20pt;
}
h3 {
    text-align: center;
    color:#211b1c;
    Font-size: 15pt;
}
h4 {
    text-align: center;
    color:#242526;
    Font-size: 15pt;
}
a:link {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
a:visited {
    color:#C8102E;
    text-decoration: underline;
    cursor: auto;
    font-weight: 700;
}
</style>
</head>
<body>
Dear $($User.DisplayName),<br><br>
Your Admin password was last set on <i>$($Admin.PasswordLastSet)</i>. This means your password will expire in <b>$($admin.DaysUntilExpired) Days</b>. Please reset your password before <b>$($Admin.ExpiryDate)</b><br>
<ol>
<li>Find a computer connected to the network</li>
<li>if the computer is logged in, on your keyboard press, <b>ctrl+alt+del</b> at the same time. </li>
<li>Select <b>Change Password</b></li>
<li>replace the username section with <b>$($Admin.samaccountname)</b>.</li>
<li>Enter your old password in the old password location.</li>
<li>Enter a new password in the new password location.</li>
<li>Confirm your new password in the confirm password location</li>
<li>Click Enter</li>
</ol>
<br>
<hr>
<br>
For more Information on how to change your password Please watch these videos.<br>
<a href='$TrainingLink'>How to change your Password.</a><br><br>

Thank you<br>
IT Department
</body> 
</html> 
"@  
    Send-MailMessage -To $($User.EmailAddress) -From "$ReturnEmail" -Subject "$($User.DisplayName) Password Notification" -BodyAsHtml $ReturnHTML -SmtpServer $MailServer
}