Thursday, May 19, 2022

Intune Autopilot set initial timezone for users with Powershell

I had a request to automatically set the timezone for users on autopilot provisioned devices. There are other ways to solve this that others have published, but I wasn't keen on relying on location services on the endpoint or calls out to maps APIs. I thought about hitting a public internet API to get location that way, but if you're backhauling internet at all that could be inaccurate. So posting my solution here since I did it a bit differently - maybe more simple, maybe not - but it did work for me. I used AD groups - one for each time zone, with each group having dynamic membership based on 'user.city = xyz' or 'user.city=abc' Then I deployed a per-user script that grabs the local UPN, queries AD via Graph API to find which timezone group they're in, and sets accordingly. Code is not super clean, but gets the job done for now.


#Script created May 2022 - this checks a user's group membership against dynamic Azure AD groups via Graph
#That group membership then sets the local timezone, or is ignored if they're in the 'NoTimezone' group
#The Azure AD groups are dynamic groups, which key off the 'city' attribute of users
#Example: 'Intune-Timezone-Pacific' dynamic group would have membership rules like so:
#(user.city -eq "Seattle") or (user.city -eq "Portland") or (user.city -eq "San Francisco")
#logs data to %temp% for validation


#Simple logging function
$Logfile = "$env:temp\settz_$env:computername.log"
function WriteLog {
    Param ([string]$LogString)
    $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
    $LogMessage = "$Stamp $LogString"
    Add-content $LogFile -value $LogMessage
}

#Install or import Graph Module
If (Get-Module -ListAvailable -Name "Microsoft.Graph") {
    WriteLog "Module is already installed"
}
Else { 
    Install-module "Microsoft.Graph -Scope CurrentUser" 
}

if ((Get-Module -Name "Microsoft.Graph.Users")) {
    WriteLog "Users Module is already imported"
}
else {
    import-module "Microsoft.Graph.Users"
}

if ((Get-Module -Name "Microsoft.Graph.Authentication")) {
    WriteLog "Auth Module is already imported"
}
else {
    import-module "Microsoft.Graph.Authentication"
}

WriteLog $error

## Connect Graph API ##
# Populate with the App Registration details and Tenant ID
$appid = 'yourappid'
$tenantid = 'yourtenantid'
$secret = 'yoursecret'
 
$body = @{
    Grant_Type    = "client_credentials"
    Scope         = "https://graph.microsoft.com/.default"
    Client_Id     = $appid
    Client_Secret = $secret
}
 
$connection = Invoke-RestMethod `
    -Uri https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token `
    -Method POST `
    -Body $body
 
$token = $connection.access_token

# Authenticate Graph
Connect-MgGraph -AccessToken $token

#grab current user upn
$upn = whoami /upn
$string = "Checking local user $upn on $env:computername "
WriteLog $string
$string = "Current tz on host is $(get-timezone) "
WriteLog $string

#list groups via graph api query
$groups = (Get-MgUserMemberOf -UserId $upn).additionalproperties.displayName
WriteLog $groups

#check if user is member of NoTimezone group to override logic
$notz = (Get-MgUserMemberOf -UserId $upn).additionalproperties.displayName -match 'Intune-Timezone-NoTime*'
If ($notz -eq "Intune-Timezone-NoTimezone") {
    $tz = $notz
    WriteLog "Overriding due to NoTimezone group membership and not setting local tz"
}
else {
    #grab first group listed that matches naming convention for timezone groups
    $tz = (Get-MgUserMemberOf -UserId $upn).additionalproperties.displayName -match 'Intune-Timezone*' | Select-Object -first 1
    $string = "TimeZone group found is $tz "
    WriteLog $string
}

#Just for reference Group Names/Ids
#Intune-Timezone-Pacific = "Yourgroupid"
#Intune-Timezone-Mountain = "Yourgroupid"
#Intune-Timezone-Central = "Yourgroupid"
#Intune-Timezone-Eastern = "Yourgroupid"
#Intune-Timezone-NoTimezone = "Yourgroupid"

#Set PC timezone based on results from graph api query
switch ($tz) {
    "Intune-Timezone-Pacific" { Set-TimeZone -Id "Pacific Standard Time" }
    "Intune-Timezone-Mountain" { Set-TimeZone -Id "Mountain Standard Time" }
    "Intune-Timezone-Central" { Set-TimeZone -Id "Central Standard Time" }
    "Intune-Timezone-Eastern" { Set-TimeZone -Id "Eastern Standard Time" }
    "Intune-Timezone-NoTimezone" {}
}

$string = "After script run tz on host is $(get-timezone) "
WriteLog $string
WriteLog "Errors:"
WriteLog $error

# Disconnect Graph
Disconnect-MgGraph

Monday, September 21, 2020

Cleaning empty dirs with Powershell gci, hidden files like .DS_Store, and the power of the force

I saw this handy post recently using powershell's get-childitem (gci) to remove empty folders, which is great for truly empty directories with nothing in them. However, it didn't go into a few use cases where there are hidden or tiny files left in directories (hello mac SMB clients) that you might want to ignore when cleaning things up. I wanted to add on to that code as an exercise so read on for a few ways to solve this one if you don't have truly empty directories, but need to clean things up still.

I'm not a mac guy, but one thing I've come across is macs leaving behind little hidden or special files, in EVERY directory for some odd reason. (To be fair I guess PCs also do that as well with thumbnail cache files) These tiny files everywhere are painful for the system admin, as that adds to overhead for backups and devices serving the file systems, and in the above code example this can cause the directories to be seen as 'not empty' due to the index file or stub hanging out alone there.

Note: I'm on a linux host so the file paths are different, and using PS Core 7 in my testing as a mention. Also: Do you own code testing and validation of course, and feel free to use the below code at your own risk.

So to start with here's the original PS code from the link, which catches empty directories but would not catch a directory that has a single ".DS_Store" hidden file in it for example.
(gci “C:\dotnet-helpers\TEMP Folder” -r | ? {$_.PSIsContainer -eq $True}) | ?{$_.GetFileSystemInfos().Count -eq 0} | remove-item
The catch is that by default get-childitem (gci) doesn't return hidden files, which we don't care about initially perhaps, but later in the code you run a given directory through .getfilesysteminfos().count. When you use that getfilesystemsinfos().count it'll return the count of ALL files in the directory, which includes hidden files, which then would cause those directories found initially that are containing remnants or hidden .DS_store files to not be cleaned up/deleted. This is a problem if you have .DS_store files in every otherwise empty directory. The solution is to use the force.
"Don't underestimate the force" -Darth Vader
To begin with I just refactored the code into two lines to make it easier to read and work with later. I also updated it to catch the hidden files in the first gci by using the -force parameter. So after the first gci using -force it uses the list of all directories found, and will look through each directory returned to count and remove only directories with no files whatsoever, hidden or not. This is the same end-result as the first snippet, we're just using a second gci instead of the .getfilesysteminfos().count so we can have flexibility in the following examples. (you can remove the whatif to really delete directories, and add a -confirm:$false to not prompt if desired)

Delete only completely empty directories:
$dirs = gci "/home/dandill/test" -r -Force | ? {$_.PSIsContainer -eq $True} foreach ($d in $dirs){if((gci $d -force).count -eq 0){$d | remove-item -whatif}}
Then if we want to remove the directories without any regular files but that still contain hidden files we can do that like so. (note the lack of a -force on the second line, which then doesn't count hidden files in the logic to evaluate a directory on if it's considered 'empty')

Delete all directories with no regular files, and still delete them if they have any hidden files:
$dirs = gci "/home/dandill/test" -r -Force | ? {$_.PSIsContainer -eq $True} foreach ($d in $dirs){if((gci $d).count -eq 0){$d | remove-item -whatif}}
Lastly, if we had a bunch of empty directories, some with only hidden files named ".DS_Store" in them we could also delete those by specifically excluding them in the second gci with the -exclude parameter. Basically saying to look for all files in directories, but to not look at .DS_Store files for the second gci, and if you come up with zero results from that, then delete that directory.

Delete all directories that are completely empty, or contain only the ".DS_Store" file:
$dirs = gci "/home/dandill/test" -r -Force | ? {$_.PSIsContainer -eq $True} foreach ($d in $dirs){if((gci $d -exclude ".DS_Store" -force).count -eq 0){$d | remove-item -whatif}}
You can modify that exclude parameter in the second line with wildcards or as desired of course if you have other files hanging out causing your directories to not be considered to be empty.

So there you have it, use the -force if you want to clean up directories with PS and gci but may have a hidden file or two preventing that with defaults. Hope this is helpful or useful to you!

Thursday, July 30, 2020

Powershell - divide a list up by the number of days left in week

Here's a sample powershell code snippet for taking an input via a file containing a list and dynamically dividing that up based on the number of days left till Friday (in this example). This can be used if you want to action on a pool of given items throughout the week based on the number of days left in the week. We also assuming that the final day is the 'take the entire list' day as far as the list goes.

For example: You have a list of 500 machines to update each day throughout the work week, and it's Monday. So if you throw the 500 machine list at the below script you can action on the first 100 of that list(5 days between mon-fri). Run it Tuesday and it will action on the first 125 items in the list (including the 100 from monday). This is important to be aware of - The code below ideally needs the input list to be validated at run time as it has no concept of state - it's not handling the input list possibly changing mid-week, previous runs of the code, future runs to occur, etc. So you'll want to add in code at "#Here is where you might do qualification..." to suit your needs to remove items that were already affected by previous runs, or are not valid when the script is run. Alternately if you prefer or can run against items again, I've included one commented way to do that below. Either way, be aware that the code below without modification if used for our example of 500 items, will result in selection like this:

Day Items Selected
Mon 100
Tue 125
Wed 166.6
Thur 250
Fri(all) 500

So be sure to add in your validation code (line 36), or change the list selection method as desired for your purposes (line 49). Hopefully this is a helpful example should you need to use powershell to action on a portion of a given list evenly between now and an end date/time. Being powershell, this could be used against virtual machines, identities, or anything else you might want to spread out over a given time frame (without going to the extra effort of manually dividing up the list introducing human error).


# Example powershell code snippet for taking an input via a file with a list and
# dynamically dividing that up based on the number of days left till friday.
# Can be used if you want to action on a pool of given items throughout the week.
# Virtual machines, indentities, etc.
# This example code does not do a number of things which you might think about if 
# using it for production, here are a few:
#  -Validation on the list inputted (are all the values formatted as desired, and valid)
#  -In the same vein, without additional code, additional runs of the script will
#   result in the actions being taken against the same items in the list
#  -Sorting or processing of the list in any way other than how it is in the list ingested
#  -Better logging beyond plain write-host output
#  -Dependent modules (if you need to call them)
#  -Time zones - this just uses the local result of get-date on the host executing the
#   script, so if you're crossing datelines between the script host and affected items,
#   or even timezones you might consider that
#
# Code is given as an example. Feel free to use if it's helpful to you, but please do
# do your own dilligence and validation of course.

Param (
  #Script parameters go here, this in an input file
  [Parameter(Mandatory = $true)][string]$file
)

#Set Error Action to Silently Continue
$ErrorActionPreference = 'SilentlyContinue'

#Testing variables
$testing = $false

#Read your list of whatever for this week
$list = get-content $file
$totalcount = $list.count

#Here is where you might do any qualification, or drop items from the list that
#don't require action, were already actioned on by a previous run, etc.

#Store variable for today and figure out number of days till friday
$today = get-date
$fridaydate = Get-Date
while ($fridaydate.DayOfWeek -ne "Friday") {$fridaydate = $fridaydate.AddDays(1)}
$daysleft = (New-TimeSpan -Start $today -End $fridaydate).days

#Divide list count by days left till Fri and compile a list for today
$numbertoactionon = $totalcount / $daysleft
$numbertoactiontoday = $list[0..$numbertoactionon]

#Alternately if you wanted to assume mon-fri run once daily, and idempotency of
#your actions below then you could use something like this to select an addition
#one fifth of the total items each run/day:
#$numbertoactionon = ($totalcount/5) * (6-$daysleft)
#$numbertoactiontoday = $list[0..$numbertoactionon]


#If it's the last day - in this example Fri, then we're grabbing all the items left
if ($daysleft -le 1){
$numbertoactiontoday = $list
}

#Loop through today's items in the list and output to screen while taking action
foreach ($i in $numbertoactiontoday){
 if($testing){
 #Insert your test case code here
 Write-host "Testing $i"
 #Do some testing
 }else{
 #Insert your prod (non-test) code here to do whatever
 Write-host "Processing $i"
 #Do something for real
 #Sleep after each individual action, just to space things out over time if desired
 write-host "sleeping 60 seconds"
 start-sleep 60
 write-host "onward!"
 }
}

Wednesday, July 29, 2020

Terraform cloud region selection based on country

Here's a basic example of terraform code to automatically select a given cloud region based on a pre-configured mapping and the public IP of the machine running terraform. I will walk through the code pieces below:

The first piece of code sends a request to an internet accessible geoip provider. (I just picked one out there, there are many be aware of free restrictions if using in prod) This http request is made via the terraform http provider which then stores the JSON response given in data.http.geoipdata. You can pop open that URL in your browser to see the data it responds with.

# Grab current region info to use for auto choosing a region
data "http" "geoipdata" {
  url = "http://www.geoplugin.net/json.gp"

  # Optional request headers
  request_headers = {
    Accept = "application/json"
  }
}
Once we have gotten the response from that provider and it's told us which country we are in we can later use that to find a cloud region. We have a variable to control if this functionality is on or not and it's set to on by default

# Variable to enable auto region select
variable "enable_autoregion" {
  description = "If set to true, enable auto region choosing"
  type        = bool
  default     = true
}
We then build a map for which cloud region we will associate with which source IP's country. So for example, if the client is in Singapore (SG) or Malaysia (MY) then we want to use region "southeastasia" or for US clients, use "westus2"

# This maps country code to azure region
variable "regionmap" {
  type = map(string)
  default = {
    "SG" = "southeastasia"
    "MY" = "southeastasia"
    "US" = "westus2"
  }
}

Lastly, we bring it all together using a local value. Here we are saying, check if var.enable_region is true. If it is, then we take our response from data.http.geoipdata.body, decode it as jSON, grab the value it gave us for "geoplugin_countrycode", and then use that to match the value in our code above var.enable_autoregion. So the value returned/set for the local value for someone in "US" would be "westus2". Then at the end of the expression, if for some reason var.enable_autoregion is not set to true, then we will set local.localregion to "eastus" as a fallback.

# This matches the country code to to the map above to return the desired cloud region if the enable_autoregion var is true, otherwise defaults to eastus
locals {
localregion = var.enable_autoregion == true ? var.regionmap[(jsondecode(data.http.geoipdata.body)).geoplugin_countryCode] : "eastus"
}
So that can be then used in your code for the region, in a basic terraform-defined Azure RG, that looks like this:

# Set up terraform resource group
resource "azurerm_resource_group" "tfrg" {
        name = "yourrgname"
        location = local.localregion
}
Github code where I used this to stand up an azure instance is here.

Thursday, April 27, 2017

SCCM Notify Users of Stale Workstations Script

This is a script that will check for members of the stale workstations collection and email the assigned user of each workstation, requesting that they connect their machine

# ------------------------------------------------------------------------------------------------------------------------------------------
# Script to gather members of the stale workstations collection and email the assigned user of each workstation
# Dan Dill, Jan 2017
# ------------------------------------------------------------------------------------------------------------------------------------------

# Import the modules that we need
Import-Module ActiveDirectory
cd "C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin"
Import-Module .\ConfigurationManager.psd1
cd CAS:

# Variables here
$SiteServer = 'yoursccmserver'
$SiteCode = 'xxx'
$CollectionName = 'Your Stale Workstations Collection'
$testing = "Disabled" # Set to Disabled to Email Users
$testRecipient = "testuser@domain.com"
$fallbackuser = "na\helpdeskuser" # This is used if the computer doesn't have a user affinity
$fallbackemail = "helpdeskemail@domain.com" # This is used if the affinity user's email address is blank
$from = "Company IT "
$smtpServer="yourmailserver.domain.com"
$logFile = "C:\temp\yourlog.log" # ie. c:\mylog.csv
$logging = "Enabled" # Set to Disabled to Disable Logging
#

# Check Logging Settings
if (($logging) -eq "Enabled")
{
# Test Log File Path
    $logfilePath = (Test-Path $logFile)
    if (($logFilePath) -ne "True")
    {
        # Create CSV File and Headers
        New-Item $logfile -ItemType File
        Add-Content $logfile "Date Run On,StaleComputer,AffinityUser,AffinityEmail,EmailSentTo"
    }
}

#Get collection of stale computer accounts
$Collection = Get-WmiObject -ComputerName $SiteServer -Namespace  "ROOT\SMS\site_$SiteCode" -Class SMS_Collection | where {$_.Name -eq "$CollectionName"}
$SMSClients = Get-WmiObject -ComputerName $SiteServer -Namespace  "ROOT\SMS\site_$SiteCode" -Query "SELECT * FROM SMS_FullCollectionMembership WHERE CollectionID='$($Collection.CollectionID)' order by name" | select *

#Main part of script here.  Goes through each member of the collection, tracks down the email adderess of the user connected to that
#collection member and then emails that email address
foreach ($Clientname in $SMSClients.name){

 #Get SCCM affinity-ed username associated with the computer name, selecting the first result as for some reason some PCs return multiple users
 $ClientUserName = (Get-CMUserDeviceAffinity -DeviceName $Clientname | select -first 1).uniqueusername
 $clientaffinityuser = $ClientUserName
 #Check to see if there is no user affinity with the computer, if blank then sub in from fallbackuser
 if (!$ClientUserName){$ClientUserName = $fallbackuser}

 #Get the email address for the affinity user
 $useremailaddress = (get-aduser $ClientUserName.substring(3) -Properties mail).mail
 $clientaffinityemail = $useremailaddress
 if (!$useremailaddress){$useremailaddress = $fallbackemail}
 # If Testing Is Enabled - Email Administrator
 if (($testing) -eq "Enabled"){$useremailaddress = $testRecipient}

 #Build content of notification email
 $subject = "IT Notification about $clientname "
 $firstname = (get-aduser $ClientUserName.substring(3)).givenname
 $computer = $clientname
 $body = "
 Dear $firstname,
 

Your Company computer $computer has not been connected to the Company network in 90 days or more.

Please connect this computer to the internal network at a Company office or via a remote VPN connection at your earliest convenience. Periodic connections to the Company network are necessary so that your computer can receive critical updates and definitions. These updates keep your computer running reliably and keep the company secure from viruses and malware. PCs that are not kept up to date and periodically connected to the Company may be disabled by the IT department.

If you have received this notification in error or if you have any questions or concerns please contact your local IT support person or the Help desk.

Thanks for helping to keep the Company secure.

IT

" #send email out Send-Mailmessage -Encoding UTF8 -smtpServer $smtpServer -from $from -to $useremailaddress -subject $subject -body $body -bodyasHTML -priority High #log results to log file if (($logging) -eq "Enabled") { $date = get-date Add-Content $logfile "$date,$clientname,$clientaffinityuser,$clientaffinityemail,$useremailaddress" } } # End of script

SCCM Application Request Notification Script

This is a script that will check for application requests in SCCM and send an email notification to a manager or approver


#####################################################################
### E-Mail-Notification for Application-Request in ConfigMgr 2012 R2
### original by Andre Picker - www.clientmgmt.de
### modified by Dan Dill
#####################################################################

### E-Mail Settings #################################################

$SmtpServer = "mailserver.domain.com"
$SenderMail = "configmgr-noreply@yourdomain.com"
$TargetMail = "whotosendto@yourdomain.com"
$Subject = "SCCM Application Catalog Request"
$Message = "You have received a new Application Request from System Center Configuration Manager:`n"
$Footer = "To process the request go to: \Software Library\Overview\Application Management\Approval Requests.`n`nOnce action has been taken please notify the application requester. `n`n*** This is an automatically generated email. Please do not reply to this message. ***"

### Queryinterval ####################################################

$interval = $(Get-Date).AddDays(-7)

### Get SMS.Sitecode #################################################

$smsproviderloc = "select * from sms_providerlocation"
$sitecode = Get-WmiObject -Query $smsproviderloc -Namespace "root\sms" -ComputerName YourSCCMServer
$sitecode = $sitecode.sitecode

### Query ############################################################

Get-WmiObject -Namespace "root\SMS\Site_$sitecode" -ComputerName YourSCCMServer -Class SMS_UserApplicationRequest | where {$_.CurrentState -match "1" -and [Management.ManagementDateTimeConverter]::ToDateTime($_.LastModifiedDate) -gt $interval} | Foreach-Object {

$User = $_.User
$Application = $_.Application
$Comments = $_.Comments
$Date = [Management.ManagementDateTimeConverter]::ToDateTime($_.LastModifiedDate)

Send-MailMessage -From $SenderMail -Subject "$Subject from $User" -To $TargetMail -Body "$Message`nUser: $user`nApplication: $Application `nDate: $Date `nComments: $Comments `n`n$Footer" -SmtpServer $SmtpServer
}



Tuesday, April 18, 2017

Password Expiration Email Notification Powershell Script

This is a script that will check specific domains in an AD forest and send password notifications out to people before their password expires. The notification email is sent in the user's native language based off the AD attribute for that user. This is based off this script here but with added functionality.
#################################################################################################################
# 
# Original Source: https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27#content
# 
# Script to Send Automated Email Reminders when Users Passwords are due to Expire.
#
# Requires: Windows PowerShell Module for Active Directory
#
# Modified by Dan Dill to support multiple domains through discovery and notifications in multiple languages based 
# on user country lookup.
#
# For country codes see: http://userpage.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
#
# http://danstechnotes.blogspot.com
# 
##################################################################################################################
# Please Configure the following variables....
$smtpServer="yourmailserver"
$from = "IT <helpdesk-noreply@eample.com>"
$logging = "Enabled" # Set to Disabled to Disable Logging
$logFile = "C:\temp\pwdchgnotification.log" # ie. c:\mylog.csv
$testing = "Disabled" # Set to Disabled to Email Users
$testRecipient = "testrecipient@example.com"
$date = Get-Date -format yyy-MM-dd
$adforest = "adforest.example.biz"
$domainstoexclude = "excludeddomain.example.biz"
# You will also want to scroll down and configure the body of the email notification that goes out to people to match
# your password policy and desired verbiage.
#
##################################################################################################################
#Actions begin here


# Check Logging Settings
if (($logging) -eq "Enabled")
{
    # Test Log File Path
    $logfilePath = (Test-Path $logFile)
    if (($logFilePath) -ne "True")
    {
        # Create CSV File and Headers
        New-Item $logfile -ItemType File
        Add-Content $logfile "Date Run On(yyyy-MM-dd),Name,CountryCode,UserDomain,EmailAddress,DaystoExpire,ExpiresOn"
    }
} # End Logging Check

# Clear Variables
if ($dcs){clear-variable dcs, users}

# Import AD Module
Import-Module ActiveDirectory

# Discover all DCs in forest
$domains = (get-adforest $adforest).domains
$domains.remove($domainstoexclude)

foreach ($d in $domains)
{
    $dcs += (get-ADDomainController -Discover -Domain $d).hostname
}

# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired
# This iterates through the list of DCs, as pwdLastSet is not in the GC by default so
# we can't simply query the GC for all our users
foreach($adserver in $dcs)
{
    $users += get-aduser -filter {(UserPrincipalName -like '*') -and (mail -like '*')} -server $adserver -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress, countryCode, UserPrincipalName  |where {$_.Enabled -eq "True"} | where { $_.PasswordNeverExpires -eq $false } | where { $_.passwordexpired -eq $false }
}

$DefaultmaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

# Process Each User for Password Expiry
foreach ($user in $users)
{
    $Name = $user.Name
    $country = $user.countrycode
    $emailaddress = $user.emailaddress
    $passwordSetDate = $user.PasswordLastSet
    $PasswordPol = (Get-AduserResultantPasswordPolicy $user)

    #get domain of user by reading and stripping UPN
    $userdomain = $user.UserPrincipalName.substring($user.UserPrincipalName.indexof("@") + 1)

    # Check for Fine Grained Password
    if (($PasswordPol) -ne $null)
    {
        $maxPasswordAge = ($PasswordPol).MaxPasswordAge
    }
    else
    {
        # No FGP set to Domain Default
        $maxPasswordAge = $DefaultmaxPasswordAge
    }

  
    $expireson = $passwordsetdate + $maxPasswordAge
    $today = (get-date)
    $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
        
    # Set Greeting based on Number of Days to Expiry.

    # Check Number of Days to Expiry
    $messageDays = $daystoexpire

    if (($messageDays) -ge "1")
    {
        switch -regex ($country) 
            { 
                '124|840|36|554' {$messageDays = "in " + "$daystoexpire" + " days"} 
                '76' {$messageDays = "em " + "$daystoexpire" + " dias"} 
                '156' {$messageDays = "$daystoexpire" + " 天后"}
                '392' {$messageDays = "あと" + "$daystoexpire" + "日で"} 
                '410' {$messageDays = "안에" + "$daystoexpire" + " 날짜"}
                default {$messageDays = "in " + "$daystoexpire" + " days"}
            }
    }
    else
    {
        switch -regex ($country) 
            { 
                '124|840|36|554' {$messageDays = "today"}
                '76' {$messageDays = "hoje"} 
                '156' {$messageDays = "今天"}
                '392' {$messageDays = "本日で"}
                '410' {$messageDays = "오늘"}
                default {$messageDays = "today"}
            }
    }

    # Email Subject Set Here
    switch -regex ($country) 
    { 
        '124|840|36|554' {$subject="Your password will expire $messageDays"}  
        '76' {$subject="Sua senha vai expirar $messageDays"} 
        '156' {$subject="提醒:您的密码将在$messageDays 过期"}
        '392' {$subject="$messageDays パスワード期限満了"}
        '410' {$subject="당신의 비밀번호가 만료되면 $messageDays"} 
        default {$subject="Your password will expire $messageDays"}
    }
    
    # Country Code Quick Reference
    # 124=Canada  840=US 36=Australia 554=New Zealand
    # 76=Brazil
    # 156=China
    # 392=Japan
    # 410=Korea
    # 710=South Africa
  
    # Email Body Set Here, Note You can use HTML, including Images.
    switch -regex ($country) 
    { 
        '124|124|840|124|36|124|554|710' {$body = "
                        Dear $name,
                        <p> Your Password for your account in the domain $userdomain will expire $messageDays.<br>
                        To change your password on a PC press CTRL+ALT+Delete and choose Change Password.<br>
                        If you connect in remotely or do not use a company provided device, you may need to use
                        alternate methods to change your network password.<br><br>
                        As a reminder, a valid password will meet these requirements:<br><br>
                        <ul><li>Not contain the user's account name or parts of the user's full name that exceed two consecutive characters</li>
                        <li>Be at least ten characters in length<br>
                        <li>Contain characters from three of the following four categories:<br>
                            <ul><li>English uppercase characters (A through Z)</li>
                            <li>English lowercase characters (a through z)</li>
                            <li>Base 10 digits (0 through 9)</li>
                            <li>Non-alphabetic characters (for example, !, $, #, %)</li></ul>
                        <li>Last  password change was more than 30 days ago</li>
                        <li>Different from the last 4 passwords</li></ul><br>
                        <p>Thanks for helping to keep the company secure.<br><br>
                        IT</P>"
                  }  
        '76' {$body = "
                        Caro $name,
                        <p> Sua senha do domínio $userdomain vai expirar em $messageDays.<br> 
                        <p>Obrigado por ajudar a manter segura.<br><br>
                        IT</P>"
                  } 
        '156' {$body = "
                        $name, 您好!
                        <p> 您的域账号密码将在$messageDays 过期。<br>
                        如需更改密码请在电脑上按下 CTRL+ALT+Delete 并选择&#8220;更改密码&#8221;.<br><br>
                        IT</P>"
                  }
        '392' {$body = "
                        $name 様,
                        <p> ドメイン $userdomain におけるあなたのアカウントのパスワードが、$messageDays 期限満了となります。 <br>
                        IT部門</P>"
                  }
        '410' {$body = "
                        $name,~에게
                        <p> 도메인 $userdomain에서 당신의 계정에 대한 비밀번호가 만료가되면 $messageDays 일 지나서 만료가 되면,<br>
                        IT </P>"
                  }
        default {$body = "
                        Dear $name,
                        <p> Your Password for your account in the domain $userdomain will expire $messageDays.<br>
                        To change your password on a PC press CTRL+ALT+Delete and choose Change Password.<br>
                        If you connect in remotely or do not use a company provided device, you may need to use
                        alternate methods to change your network password.<br><br>
                        As a reminder, a valid password will meet these requirements:<br><br>
                        <ul><li>Not contain the user's account name or parts of the user's full name that exceed two consecutive characters</li>
                        <li>Be at least ten characters in length<br>
                        <li>Contain characters from three of the following four categories:<br>
                            <ul><li>English uppercase characters (A through Z)</li>
                            <li>English lowercase characters (a through z)</li>
                            <li>Base 10 digits (0 through 9)</li>
                            <li>Non-alphabetic characters (for example, !, $, #, %)</li></ul>
                        <li>Last  password change was more than 30 days ago</li>
                        <li>Different from the last 4 passwords</li></ul><br>
                        <p>Thanks for helping to keep our company secure.<br><br>
                        IT</P>"
                  }
    }

   
    # If Testing Is Enabled - Email Administrator
    if (($testing) -eq "Enabled")
    {
        $emailaddress = $testRecipient
    } # End Testing

    # If a user has no email address listed
    if (($emailaddress) -eq $null)
    {
        $emailaddress = $testRecipient    
    }# End No Valid Email

    # Send Email Message
    # This is where you want to set the number of days away from expiration for the email notification to be sent to the user
    # In the example below we send an email on the day it is expiring 0, and also 1, 3, 10, and 21 days away from expiration.
    if ($daystoexpire -eq "0" -or $daystoexpire -eq "1" -or $daystoexpire -eq "3" -or $daystoexpire -eq "10" -or $daystoexpire -eq "21")
    {
         # If Logging is Enabled Log Details
        if (($logging) -eq "Enabled")
        {
            Add-Content $logfile "$date,$Name,$country,$userdomain,$emailaddress,$daystoExpire,$expireson" 
        }
        # Send Email Message
        # Encode as UTF8 for US/AUS/CA/NZL users
        if (($country -eq '124') -or ($country -eq '840') -or ($country -eq '36') -or ($country -eq '554') -or ($country -eq '0'))
        {
        Send-Mailmessage -Encoding UTF8 -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High
        }
        else
        # Encode as unicode for all other country codes
        {
        Send-Mailmessage -Encoding Unicode -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High  
        }
    } # End Send Message
    
} # End User Processing

# End