Migrating WSUS From Server 2003 to Server 2012

List of supported Operating System’s and migration scenarios is listed here.  In my case I am going to migrating a WSUS deployment from Server 2008 R2 running SQL 2005 to Server 2012 Standard with SQL 2008 R2 SP1.

Review considerations and system requirements can be found here.

First step is to install and configure the OS according to your organization’s standards (including updates).  Second step is to install SQL 2008 R2 with SP1.

After launching Setup from the .ISO file I got the error below.  Select Run the program without getting help.

SQLError

Of course, then I just got the error below.  Back to the drawing board!  Open up Server Manager and install the .NET Framework 3.5 Feature Role using your Windows installation media.

.NETError

Open up Server Manager and install the .NET Framework 3.5 Feature Role using your Windows installation media.  You may need to specify an alternate location (which is just really your CD-ROM path).  Mine was D:SourcesSxS

After that is done re-launch the setup and install SQL Server.  I am not going to go through all the steps here, the only pieces I installed were the Database Engine and the Management Tools.  Then apply SP1 to the SQL Installation.

While SP1 is downloading/installing go through the Preparing for Migration documentation here, including filling out the worksheet for the existing WSUS server.

Now, for the actual migration we are just going to be following the steps listed here.

In my situation I am migrating from a WSUS Installation on Windows Server 2003 x86 (Source Server) to Windows Server 2012 x64 (Destination Server).   First step is to install the Windows Server Migration Tools on my destination server using the PowerShell command below.

After that you need to create a folder containing those migration tools to use on your source server following the instructions here.  Make sure you get the create command to match your Source WSUS Server.  In my case the command looked like this:

Next, open the C:WindowsServerMigration folder you just created.  You should see a folder named similarly to mine which was SMT_ws03_x86.  Copy this folder to your Source Server.  Next, open a command prompt or PowerShell and change the directory to where you placed the folder above.  In my case it was C:SMT_WS03_x86 and run the following command to register Windows Server Migration Tools:

Next step is to migrate WSUS Update Binaries from the Source Server to the Destination Server, instructions for which can be found here.  For some reason the instructions aren’t in the original article, I had to click through about 7 different TechNet articles to find these steps.

On your DestinationServer open Windows Server Migration Tools (as administrator) and run the command below:

Make sure to specify a password you can remember!

On your SourceServer open Windows Server Migration Tools (as administrator) and run the command below:

In my case my Source Path was just D:WSUSWsusContent and my Destination Path was the same.  You don’t need to create the folder structure on the Destination Server, the Migration Tool will complete that for you.

While that runs your Source and Destination Servers will look like this:

DestinationWSUS SourceWSUS

You can also check on the progress of the migration by comparing the contents of the WSUSWsusContent folder on the Source and Destination Servers to see how much has copied over.  This is a screenshot of my Destination Server after about 15 minutes of migration.

WSUSProgress

PowerShell: Add Domain User to Local Administrators Group on All Servers In An OU

A co-worker needed to add a specified user to the local administrators group to all the servers in a specific Organizational Unit (OU), across 3 different sub-domains. Because of the different domains and service account names the script required that I prompt the user for input. After several hours (I am still very much learning PowerShell), this is what I came up with, and it worked like a charm.  Any suggestions/recommendations are greatly appreciated.

This blog was extremely helpful in piecing together the last parts that I needed.

PowerShell: Make Changes to CSV for Import Into ADLS Instance

We have an Active Directory Lightweight Directory Services (ADLS) instance that houses users for a custom application.  When new users need to be added to the instance we get a spreadsheet with the user’s names and their usernames.  I needed to figure out a way to import this .CSV, add columns with the appropriate information for import into the ADLS instance.

This is what I came up with.  If there is a “better” or more compact way to do this please feel free to let me know how to do so in the comments.  This is one of the first PowerShell scripts I have ever written and I have a lot to learn.

 

Updating PowerShell v3 Using a Proxy

Because my company uses a proxy to access the internet, the Update-Help doesn’t work in PowerShell.  And since it’s a company wide proxy, I can’t just go to a machine without it and download the help files and then import them into PowerShell.  After some digging around I found the solution below.  It’s not marked as the answer in this TechNet article, but it did the trick for me.

$webclient = New-Object System.Net.WebClient
$creds = Get-Credential
$webclient.Proxy.Credentials = $creds
update-help

Remotely Restart Services Using Powershell & A Batch File

Problem:

We have an order printing process utilizing a third party application that has registered Windows services to control the printing in the Warehouse.  For unknown reasons these services randomly stop printing, even though the services are running.  The fix is to just restart them.  I have setup a scheduled task to restart them daily using the same Powershell script below, but what if someone needs to do them during the day, or when IT staff is unavailable, or???.

Solution:

Create a batch file to do the same thing that any user can run!

Create your batch file like normal, and then use the following  syntax to launch Powershell and run the command to restart services.

powershell.exe -command Restart-Service -InputObject $(Get-Service -Computer SERVERNAME -Name Service1,Service2,Service3);

NOTE:  You need the semi-colon on the end of the line.  If your script has multiple lines, you will need a semi colon at the end of each one.

Credit:

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.

Calendar Permissions Missing After Migration from Exchange 2003 to 2010

Situation:
All employees use Outlook 2007.  I have migrated our Exchange environment from Exchange 2003 to Exchange 2010 probably 3 months ago.

One user’s calendar (we will call him Tom)  was not showing Free/Busy information to any other user who had added Tom’s calendar to their Outlook.  In addition Tom mentioned that when he tried to edit his own calendar permissions it was throwing errors.

Troubleshooting:
I added the user’s calendar to my Outlook (which is version 2010) and nothing showed up.  I then setup an email profile for Tom on my computer and logged in and looked at his calendar permissions.  And, there were none.  Not even the default permissions, which was pretty awesome.  So I added in myself, clicked OK and it came up with the error message saying “Unable to Modify Permissions”.  I logged in as Tom through OWA and tried to modify the permissions and the same error occurred.

At this point I did what any good SysAdmin does and break out my Google Fu to see if it can shed any light on this issue, because I have no idea how to get the default permission to show up short of recreating the mailbox and I don’t quite want to go that route yet.  Sure enough after about 10 minutes of digging I came across this article (SysAdmin fail, I didn’t save the right link.  Crap.) that said one way to recreate the default permission was to add someone as a delegate and then remove.

Solution:
So I added myself as a delegate to Tom’s mailbox, closed out of Outlook, reopened Outlook and removed myself and then verified on Tom’s calendar that the default permission was now present.  I then logged into Outlook as myself and wasn’t able to view his Free/Busy information yet, but I was through OWA.  A few hours later Tom’s Free/Busy calendar information was showing in my Outlook and was for other users as well.