Import Contacts CSV Into Exchange 2010

Problem:  I need to import 700 of our “key client contacts” into Exchange 2010 from a CSV file

Solution:  Powershell!

Step 1:  Create the CSV

There are a ton of different fields you can use in your CSV to import into Exchange contacts, I am not going to go into them all here, but just check out the set-contact Powershell cmdlet for all your choices.

Because this was a fairly simple import, this is what the headers of my CSV file looked like.

Step 2:  Create the Powershell Script

 Import-CSV C:contacts.csv | ForEach {
 New-MailContact -Name $_.ContactFullName -ExternalEmailAddress $_.ContactEmail
-FirstName $_.ContactFName -LastName $_.ContactLName
-OrganizationalUnit “domain.com/Folder Structure Here”

 Set-Contact -Identity $_.ContactFullName -Phone $_.”ContactPhone” -MobilePhone $_.”ContactMobile”

 }

The first part creates a new mail contact using the mappings in the script.  So, the Name field in the Exchange contact uses the ContactFullName field in the CSV, the ExternalEmailAddress in the Exchange contact uses the ContactEmail field in the CSV, etc.

The second part sets other attributes on the contact, in this example the work and cell phone numbers of each contact by using the -Identity parameter mapped to the same ContactFullName field in the CSV used first in the script.

Step 3:  Do Something Really Dumb and Make Life Difficult For Yourself

So let’s just imagine for a minute that you are cleaning up your CSV file and notice some of your phone numbers have periods in them, and that because you are who you are, that bothers you, so you do a simple find and replace and problem solved!  You import your contacts and everything is perfect and takes about 3 minutes.  Then you notice a problem.  All of your email address look like this:  exchangecontact@exchangecom.  Oh yeah, because you took all the periods out dummy!  So, after I pulled the CSV information out of our SQL database again, I was ready to do another import, but needed to clean up the existing contacts first.  In retrospect, I think I could have just used the set-contact cmdlet to do this an easier way, but I’m new to Powershell and this is the first thing that came to mind.  And what was that?  DELETE ALL OF THE CONTACTS I JUST IMPORTED!

 Import-CSV C:contacts2.csv | ForEach {
 Remove-MailContact -Identity $_.ContactFullName -Confirm:$false
 }

That little script used an identical CSV that I had created to delete all of the contacts by using their ContactFullName as their Exchange identity.  Then just re-run your import script and you are good to go.

I could have used a script like this to just update the existing contacts:

Import-CSV C:contacts3.csv | ForEach {
 Set-Contact -Identity $_.ContactFullName -ExternalEmailAddress $_.ContactEmail
 }

Unable to Connect to VMWARE View Desktop Through Security Gateway After Upgrade to 5.0

Upgraded my VMWARE environment to 5.0 this weekend, including all VMWARE View components.  Couldn’t have gone any smoother, except that externally, users were unable to connect using the client (either 4.6 or 5.0).  The desktop would attempt to load but after about 10 seconds would fail with the error “the connection to the remote computer ended”.

Come to find out, it was an easy fix, but VMWARE couldn’t explain to me how this happened during the upgrade. Somehow the settings on my Security Gateway were changed during the upgrade.

This is what my Security Gateway settings were set to inside the View 5.0 Manager:

The HTTP(S) Secure Tunnel URL was set to:
https://securityservername.domain.com:443

The PCOIP Secure Gateway was set to the internal IP address of the View Security server.

Changing these settings back to their original ones resolved the issue.  The person I spoke with at VMWARE Support is going to look into how these settings got changed during the upgrade process, because they certainly shouldn’t have been changed.

Symantec Endpoint Protection and VMWare View Desktops

Very long story short, when setting up our VMWare View environment using Windows 7 desktops with Symantec Endpoint Protection (SEP) I had all kinds of problems when composing pools with the customization stage failing because the PCs were unable to join the domain.  I spent A LOT of time troubleshooting with VMWare and on my own time, and someone at VMWare, when working on an entirely different problem came up with this fix.

First, we need to create 2 .bat files

Name this PostSync.bat
sc config “Symantec Antivirus” start= auto

Name this PowerOff.bat
sc stop “Symantec Antivirus”

Save these files somewhere on your base template.  I put mine in C:SEP.  Snapshot the template when you are done.  Before you recompose your pool, we need to first modify the pool settings.

In View Manager, go to the Pool you want to recompose.  Click the guest customization tab.  You should set it to look like the following, just modify the path to whatever is applicable in your environment.

What this will do is on power off, it will stop the Symantec Antivirus service so that the desktop can do all of it’s re-configuring and customization.  After that is all done the other script will tell that service to start up and SEP will act as if it was never turned off.

Windows 7 Password Notifications

Situation:

Users who use Windows 7 virtual desktops (in my case, VMWARE View desktops) are not able to see password expiration notifications when logging in.  In Windows 7 instead of getting a pop up box you just get a little flash down in lower right hand corner, and if you aren’t paying attention you can miss it very easily.  So I got tired of having to reset user passwords and decided to do something about it.

Solution:


Step 1: Create a Windows GPO to display a pop up box when password is close to expiration
Step 2: Create a Windows Powershell script to run as a scheduled task that emails users when their password is expiring

Create a new Windows GPO.  Credit to this guy.  I called mine Password Expiration Notification.  Set WMI Filtering to Windows 7.  Set Security Filtering to Authenticated Users and Domain Computers.

On the settings tab, configure the settings to look like this.

The script location can be whatever you want of course, so change that accordingly.  The passexpiry.vbs script looks like this. 
On Error Resume Next
set objNetwork=CreateObject(“Wscript.Network”)
Set objUser=GetObject(“WinNT://” & objNetwork.Userdomain & “/” & objNetwork.Username & “,user”) 
Dim objUser 
PassExp=INT(objUser.MaxPasswordAge/86400)-INT(objUser.PasswordAge/86400) – 1
if (PassExp=<14) and (PassExp>0) then
    wscript.echo “Your password is due to expire in ” & PassExp & ” day(s)”
end If

After that is all done, just attach it to the correct OU’s and wait for propagation to do its thing!
Some might think this is overkill, but as another layer you can also send the users an email saying their password is expiring!
First, create the Powershell script:
# PSPwdExpires.ps1
# PowerShell script to find all user accounts where the password
# is about to expire in a specified number of days.
#
# ———————————————————————-
# Copyright (c) 2011 Richard L. Mueller
# Hilltop Lab web site – http://www.rlmueller.net
# Version 1.0 – March 23, 2011
# Version 1.1 – April 6, 2011 – Added email function.
#
# This program assumes there is one password policy for the domain. The
# program finds all users whose password will expire in the specified
# period.
#
# You have a royalty-free right to use, modify, reproduce, and
# distribute this script file in any way you find useful, provided that
# you agree that the copyright owner above has no warranty, obligations,
# or liability for such use.
Trap {“Error: $_”; Break;}
# Specify number of days. Any users whose passwords expire within
# this many days after today will be processed.
$intDays = 14
# Email settings.
$Script:From = “username@domain.com
$Script:Subject = “Password Expiration Notice”
$Server = “exchangeserver.domain.com”
$Port = 25
$Client = New-Object System.Net.Mail.SmtpClient $Server, $Port
# You may need to provide credentials.
$Client.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
Function SendEmail($To, $Body)
{
    $Message = New-Object System.Net.Mail.MailMessage `
        $Script:From, $To, $Script:Subject, $Body
    $Client.Send($Message)
}
# Retrieve Domain maximum password age policy, in days.
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]”LDAP://$D”
$MPA = $Domain.maxPwdAge.Value
# Convert to Int64 ticks (100-nanosecond intervals).
$lngMaxPwdAge = $Domain.ConvertLargeIntegerToInt64($MPA)
# Convert to days.
$MaxPwdAge = -$lngMaxPwdAge/(600000000 * 1440)
# Determine the password last changed date such that the password
# would just now be expired. We will not process any users whose
# password has already expired.
$Now = Get-Date
$Date1 = $Now.AddDays(-$MaxPwdAge)
# Determine the password last changed date such the password
# will expire $intDays in the future.
$Date2 = $Now.AddDays($intDays – $MaxPwdAge)
# Convert from PowerShell ticks to Active Directory ticks.
$64Bit1 = $Date1.Ticks – 504911232000000000
$64Bit2 = $Date2.Ticks – 504911232000000000
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = “subtree”
# Filter on user objects where the password expires between the
# dates specified, the account is not disabled, password never
# expires is not set, password not required is not set.
# and password cannot change is not set.
$Searcher.Filter = “(&(objectCategory=person)(objectClass=user)” `
    + “(pwdLastSet>=” + $($64Bit1) + “)” `
    + “(pwdLastSet<=" + $($64Bit2) + ")" `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=2)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=65536)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=32)” `
    + “(!userAccountControl:1.2.840.113556.1.4.803:=48))”
$Searcher.PropertiesToLoad.Add(“sAMAccountName”) > $Null
$Searcher.PropertiesToLoad.Add(“pwdLastSet”) > $Null
$Searcher.PropertiesToLoad.Add(“mail”) > $Null
$Searcher.PropertiesToLoad.Add(“proxyAddresses”) > $Null
$Searcher.SearchRoot = “LDAP://” + $Domain.distinguishedName
$Results = $Searcher.FindAll()
ForEach ($Result In $Results)
{
    $Name = $Result.Properties.Item(“sAMAccountName”)
    $PLS = $Result.Properties.Item(“pwdLastSet”)
    $Mail = $Result.Properties.Item(“mail”)
    $Addresses = $Result.Properties.Item(“proxyAddresses”)
    If ($PLS.Count -eq 0)
    {
        $Date = [DateTime]0
    }
    Else
    {
        # Interpret 64-bit integer as a date.
        $Date = [DateTime]$PLS.Item(0)
    }
    # Convert from .NET ticks to Active Directory Integer8 ticks.
    # Also, convert from UTC to local time.
    $PwdLastSet = $Date.AddYears(1600).ToLocalTime()
    # Determine when password expires.
    $PwdExpires = $PwdLastSet.AddDays($MaxPwdAge)
    # Determine email address.
    If (“$Mail” -eq “”)
    {
        ForEach ($Address In $Addresses)
        {
            $Prefix = $Address.SubString(0, 5)
            If (($Prefix -ceq “SMTP:”) -or ($Prefix -ceq “X400:”))
            {
                $Mail = $Address.SubString(5)
                Break
            }
        }
    }
    If (“$Mail” -ne “”)
    {
        $Notice = “Password for user $Name must be changed by $PwdExpires”
        SendEmail $Mail $Notice
        “Email sent to $Name ($Mail), password expires $PwdExpires”
    }
    Else
    {
        “$Name has no email, but password expires $PwdExpires”
        “DN: $DN”
    }
}

Once this has been done save it somewhere locally on the machine you will be running the scheduled task from.
Next, create the scheduled task.  On the actions tab, you want to start a program:
%SystemRoot%system32WindowsPowerShellv1.0powershell.exe
In the add arguments field, you want to put the path to the script.  In my case:
C:ScriptsPSPwdExpires.ps1
Set it to run at a scheduled time every day, and you are good to go.