Manage SSL/TLS settings in IE

The other day I had the need to configure SSL/TLS settings in IE for users in my organization. While I’m aware of the easy methods of managing IE settings using group policy, I always prefer to use Group Policy Preferences to manage the settings instead. After a bit of digging I identified that to manage the SSL/TLS settings you simply had to change the value of ‘SecureProtocols’ at HKCUSoftwareMicrosoftWindowsCurrentVersionInternet Settings.

Below are the possible values that you can use and what they do.

0 – Disable SSLTLS
8 – SSL 2.0
32 – SSL 3.0
40 – SSL 2.0, SSL 3.0
128 – TLS 1.0
136 – SSL 2.0, TLS 1.0
160 – SSL 3.0, TLS 1.0
168 – SSL 2.0, SSL 3.0, TLS 1.0
512 – TLS 1.1
520 – SSL 2.0, TLS 1.1
544 – SSL 3.0, TLS 1.1
552 – SSL 2.0, SSL 3.0, TLS 1.1
640 – TLS 1.0, TLS 1.1
648 – SSL 2.0, TLS 1.0, TLS 1.1
672 – SSL 3.0, TLS 1.0, TLS 1.1
680 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1
2048 – TLS 1.2
2056 – SSL 2.0, TLS 1.2
2080 – SSL 3.0, TLS 1.2
2088 – SSL 2.0, SSL 3.0, TLS 1.2
2176 – TLS 1.0, TLS 1.2
2184 – SSL 2.0, TLS 1.0, TLS 1.2
2208 – SSL 3.0, TLS 1.0, TLS 1.2
2216 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.2
2560 – TLS 1.1, TLS 1.2
2568 – SSL 2.0, TLS 1.1, TLS 1.2
2592 – SSL 3.0, TLS 1.1, TLS 1.2
2600 – SSL 2.0, SSL 3.0, TLS 1.1, TLS 1.2
2688 – TLS 1.0, TLS 1.1, TLS 1.2
2696 – SSL 2.0, TLS 1.0, TLS 1.1, TLS 1.2
2720 – SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2
2728 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2

Values were obtained from:

Working with Outlook using PowerShell

There was recently a request by an end user to have email items in a particular folder automatically deleted after they were older than X number of days. Right off I knew this could be done with the Outlook.Application com object, however I didn’t know exactly how. After a bit of digging and messing around I found that it could be done, and fairly easily at that.

The first step is establishing a variable and connecting it to the Outlook.Application com object like so.

$outlook = new-object -comobject “Outlook.Application”

You then need to establish a variable for the mapi namespace.

$mapi = $outlook.getnamespace(“mapi”)

At this point you’re essentially connected to the mailbox for Outlook. You can navigate through the objects in the mailbox by using GetDefaultFolder(), putting the appropriate number in the parenthesis. I’ve mapped out the default objects, listed below. Depending on your situation you might have more.

3 – Deleted Items
4 – Outbox5 – Sent Items
6 – Inbox
9 – Calendar
10 – Contacts
11 – Journal
12 – Notes
13 – Tasks
16 – Drafts
19 – Conflicts
20 – Sync Issues
21 – Local Failures
23 – Junk E-Mail
25 – RSS Feeds
28 – To-Do List
30 – Suggested Contacts

To connect to the Inbox folder, you would do:

$inbox = $mapi.GetDefaultFolder(6)

Now, connecting to a sub-folder doesn’t appear to be as straightforward as I thought it would be. So far the only way I’ve been able to connect to a sub-folder is by doing the following.

$subfolder = $inbox.Folders | Where-Object {$_.Name -eq “SubFolderName”}

You can then work with the sub-folder using the $subfolder variable. If you want to list all the items in the sub-folder, you can do:


If you’re only interested in items older than 1 day, you can do:

$date = (Get-Date).AddDays(-1)
$subfolder.items | Where-Object {$_.SentOn -lt $date}

Back to what I was trying to figure out, which is delete items in a folder that are X numbers of days old, you can delete items like so:

$date = (Get-Date).AddDays(-1)
$subfolder.items | Where-Object {$_.SentOn -lt $date} | ForEach-Object {$_.Delete()}

As I find more things worth mentioning that you can do with the Outlook com object I’ll be sure to add them.

Working with ACL’s in PowerShell

I recently had the need to make some permission changes to a folder and decided to take the time to figure out how to do it with PowerShell without using Cacls. The sources I used will be at the bottom of this post.

The first thing you need is a folder. For this we’ll use a folder called “Test” on C:, so “C:Test”. You then need to use Get-Acl to aquire the security descriptor for the folder.

$acl = Get-Acl “C:Test”

At this point you can call the $acl variable and you’ll get back a table that specifies the Path, Owner, and Access for the folder “Test”. To get a more friendly view of the Access column, call $acl.Access. You’ll then get a nice breakdown of each rule.

Now that we have information on the folder we can start making changes. The first thing I’ll cover is working with configuring inheritable permissions. For this we’ll use the ‘ObjectSecurity.SetAccessRuleProtection’ method. There are two parameters to this method, ‘isProtected’ and ‘preserveInheritance’. ‘isProtected’ is what determines if a folder inherits permissions. A value of true will break inheritance, while a value of false will allow inheritance. ‘preserveInheritance’ is only used if ‘isProtected’ equals true. When ‘isProtected’ equals false, the parameter ‘preserveInheritance’ still needs to be set, however it will be ignored. If you’re breaking inheritance on the folder, this parameter determines if the existing permissions are retained or removed. A value of true will preserve the existing access rules, while a value of false will remove existing access rules. An example of using this method to allow inheritance is below.


Here’s an example of using the method to remove inheritance but copy existing access rules.


And here’s an example of using the method to remove inheritance and also remove existing access rules.


Now onto configuring access rules. Let’s assuming that for this folder we’ve gone with leaving inheritance in place. By default we have the following existing access rules.

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITYSYSTEM
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTINAdministrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

Lets say we want to change the access rules so that the built in Users group has modify access to the folder. The first thing we need to do is create a variable that will store the new access rule. For this we’re going to use the ‘FileSystemAccessRule’ class. This class has 7 properties, however we’re only going to need to use 5 to give the Users group modify access. The properties we’ll use are:

  • IdentityReference: The identity that the access rule will apply to. In this case, it will be “Users”.
  • FileSystemRights: The access being given. In this case it will be “Modify”.
  • InheritanceFlags: Determines how this rule is inherited by child objects (folders and files). In this case it will be “ContainerInherit, ObjectInherit” (Container meaning folder, Object meaning file).
  • PropagationFlags: Determines how inheritance of this rule is propagated to child objects. In this case it will be “None”.
  • AccessControlType: Specifies whether a access rule is used to allow or deny access. In this case it will be “Allow”.

Putting this all together you’ll get this:

$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(“Users”,”Modify”,”ContainerInherit, ObjectInherit”,”None”,”Allow”)

You then need to add the rule to the $acl variable we already created by doing this:


That’s all there is to creating new access rules and applying them to the $acl variable, however that’s not all you have to do to apply the access rule and the inheritance settings to the folder. I”ll get to the step that applies the changes right after we go over making changes to the ownership.

When working with ownership you have to use the ‘ObjectSecurity.SetOwner’ method.  To set the owner to the local Administrators group, we would run:

$acl.SetOwner([System.Security.Principal.NTAccount] “Administrators”)

In the end, once you’ve finished with making your modifications, you need to apply your changes to the folder. To do this we use the ‘Set-Acl’ cmdlet like so:

Set-Acl “C:Test” $acl

After you do that, you can relist your access rules and you’ll see that the Users group now has modify access in addition to the existing inherited rules. Just to point it out, you’ll notice that the access rule for granting the modify access will have the ‘IsInherited’ flag set to False.

FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITYSYSTEM
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTINAdministrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

And that’s all there is to it.

My sources for figuring this all out are below:

Blog post from Jose Barreto on TechNet that pointed me in the right direction (while this article does describe how to do the above as well, I feel my post is much more clear and to the point of how to do this)

MSDN article on the FileSystemAccessRule class

MSDN article on the SetAccessRuleProtection Method

Working with data tables in PowerShell

I’m creating this post for my own reference for the most part, however hopefully it will assist someone else as well.

The following can be ran in PowerShell to create a basic data table:

$Table = New-Object System.Data.DataTable

For each column you want in the table, you’ll want to run something like the following:

$column = New-Object System.Data.DataColumn ‘NameofColumn’,([COLUMNTYPE])

Or to simplify things:

$Table.Columns.Add((New-Object System.Data.DataColumn ‘NameofColumn’, ([COLUMNTYPE])))

In the above, ‘NameofColumn’ is whatever you want the column title to be. It can contain spaces and symbols. COLUMNTYPE should be replaced with the type of column you want it to be. I most commonly use ‘string’ and ‘datetime’. You can reference for additional column types.

After you’ve created your table with the columns you want, you’ll need to start populating the table. The code below will show you how to add a row to the table, and assumes there are 3 columns, Name, Department, and Email.

$row = $Table.NewRow()
$row.’Name’ = “John”
$row.’Department’ = “IT”
$row.’Email’ = “”

If you find yourself needing to edit  a row, you have two ways IMO that you can do this. The first way is to explicitly call a particular row. You would do this by calling:


In the above, the [0] references the first row. Using [1] would reference the second row, and so on. If you were to run:


You would get the value for the Name column of row 1 ([0]) returned. You can do the following to change the value.

$Table.Rows[0].Name = “NewName”

To delete the row you would run:


The second way you can change a value is a bit more useful. You can essentially search the table for the row you want, and then change the value of a column.

To do this you use Where-Object. The following example will change the department column for the row where the name is equal to “Joe”.

($Table.Rows | Where-Object {($_.Name -eq “Joe”)}.Department = “NewDepartment”

Finally, if you find yourself needing to delete a row you can do so using the same two methods we used for modifying a row/column. You can delete a row by running:


and also by doing:

($Table.Rows | Where-Object {($_.Name -eq “Joe”)}).Delete()

Managing NPS using PowerShell and netsh

The following post will detail how you can use a combination of PowerShell and netsh to manage one or more Microsoft NPS servers (Server 2008 R2). In the example I’ll detail how you can update the shared secret on all the RADIUS clients across all your NPS servers.

The first thing you need to do in such a PS script is to have a way of identifying your NPS servers. If you’re running a small environment where all the NPS servers are going to need to be setup the same way, then this is easy and I’ll explain what I’ve done below. If you’re running a larger more complex environment where you have groups of NPS servers setup differently, then you’re likely going to want to create some config files for your script to pull server names from.

Assuming you have a small environment… make sure you have your NPS servers registered. To register an NPS server, launch the ‘Network Policy Server’ console, right click the top node ‘NPS (local)’ and click ‘Register server in Active Directory’. This will cause the servers computer account in Active Directory to be added to the security group ‘RAS and IAS Servers’. You can then have your PS script pull a list of NPS servers from this group using a line like

$NPSServers = Get-ADGroupMember “RAS and IAS Servers”

The next step is to build an array of all the Radius clients you have on your NPS servers. Ideally you would already have the same Radius clients on each NPS server, however if you happened to have a Raidus client setup on one NPS server and not on another it’s not a super big deal in regards to having the script update the shared secret.

To build the array of the Radius clients, the first thing you’ll want to do is create the array object.

$ClientArray = @()

You then will want to do a ForEach on your list of NPS servers and collect an export of the Radius clients using netsh. The below can be used to do this.

$NPSServers | ForEach-Object {
$Server = $_.Name
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
netsh nps show client

This will populate the variable Result with an export of all the clients. You’ll then have to parse through the result variable and pull out the client information. The below shows a way (maybe not the best way) to pull out the name of each client on the NPS server, along with the other information on the client.

$Result|ForEach-Object {


If ($currenttext-like“*Client configuration*”) {

$nameline= ($currentline+ 2)


$IPline= ($currentline+ 3)


$stateline= ($currentline+ 4)


$secretline= ($currentline+ 5)











If ($Global:ClientArray-contains$String) {



Else {






Just for the record, I’m well aware that the above code could be improved upon. I just copy and pasted that from the first version of a utility I wrote and have never gone back to review and clean it up. It works, so… yeah.

After the above has been ran against each NPS server, you’ll have an array that contains a list of Radius clients without any duplicate names. You can then run the following to go through each client in the array on each server and update the shared secret.

$ServerLoop= 1

$ServerArray|ForEach-Object {


Log (“Currently processing NPS server $CurrentServer.”)

$ClientLoop= 1

$Global:ClientArray|ForEach-Object {

Status (“Client $ClientLoop of $ClientCount. Server $ServerLoop of $ServerCount.”)


Log (“Updating secret on RADIUS client $Name on $CurrentServer.”)

$Result=Invoke-CommandComputerName $CurrentServerScriptBlock {



netsh nps set client name =$Name sharedsecret =$Secret

} ArgumentList $Name, $NewSecret

If ($Result-like“*Ok.*”) {

Log (“The client $Name successfully received the new secret on $CurrentServer.”)


Else {

Log (“ERROR: A problem occured while updateing the secret for $Name on $CurrentServer.”)








As you can see, the main thing to note is the use of netsh to specify the name of a Radius client, and then specify the new shared secret. Below are some other netsh commands you can use to manage your Radius clients using PS scripting like what you saw above.

Add a client

netsh nps add client name = $ClientName address = $ClientIP state = enable sharedsecret = $secret

Edit a client name

netsh nps rename client name = $Name newname = $NewName

Edit other details of a client

netsh nps set client name = $name address = $IP state = $status sharedsecret = $secret

Delete a client

netsh nps delete client name = $Name


Alternatively to having a bunch of PowerShell scripts that do this stuff for you, you can create a PS Forms based app that provides you central management of your NPS servers. Below are some screenshots of what I’ve created for the network team at my current employer to use to do day to day tasks with the NPS servers we have.

The screenshot below is of the main page of the app. From here you can add/edit/delete clients, as well as view the radius logs combined from all the NPS servers, update the shared secret across the board, and view information on a specific NPS server.


The screenshot below is of the interface to create a new radius client. When the create button is pressed, PS goes out and creates the new client on all the NPS servers.


The screenshot below is of the interface to edit a radius client. Like when creating a new client, clicking the update button causes PS to go out across all NPS servers and update information for the client on each server.


The screenshot below is of the interface for syncing an NPS server with another. This could be useful if a single NPS server seems to be out of sync from the others, or if you stand up a new NPS server. (I’m well aware of the ability to export your NPS config and then import it on new NPS servers.)


The screenshot below is the first of several interfaces for viewing the NPS logs. The screenshot below just shows a list of all the dates there are logs for on all the NPS servers. Upon clicking a date, the screen would update with a list of all the systems that have connected to the NPS servers. Clicking a system would result in a cleaned up view of the NPS logs for that system. Useful when troubleshooting connectivity issues.


Profile Picture Manager

Last week I threw together a quick form in PrimalForms that allows users to do the following:

  • View their current AD user profile picture
  • Upload a new picture from a saved JPEG image
  • Use an attached webcam to take a new picture

It can be downloaded HERE.

The code I used for capturing the webcam content was found somewhere online. I didn’t initially plan to post the finished product online, so didn’t think to save the source of code. If I happen to run across the source I’ll post it. UPDATED: SOURCE

The icons used in the form were just ones I found on a Google image search. Most certainly didn’t make them, but seeing how I’m not selling this or making any money off the hits to this site, I don’t see how anyone should have a problem with it.

If you find any problem with the form, or have suggestions for improvements post a comment.



Convert string number to numerical number

Assume that you have a number contained in a variable as a string.

$var = “2”

Now assume you want to use that variable to specify an item in an array, like so:


In PowerShell v3, you’ll get the expected outcome. However when using the above in PrimalForms (which is based on PowerShell v2) it won’t work. The string based number has to be converted to a numerical number. You can do this by running the following line:

$var = [int]$var

You’ll now have a numerical number stored in your variable.

Query against 389 Directory Services (Open Source LDAP) with PowerShell

Recently I had the need to query an LDAP server running 389 Directory Services with PowerShell. I found my way to this website -> where you get the basics of connecting to a non-Microsoft LDAP directory using PowerShell, however in my case the steps provided didn’t work. Below are the steps I took in order to successfully query information on user accounts on the 389 Directory Services server in my environment.

I start off in my script with the following lines of code.

$c = New-Object System.DirectoryServices.Protocols.LdapConnection “LDAP-SERVER-NAME-HERE”
$c.SessionOptions.SecureSocketLayer = $false
$c.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous
$credentials = New-Object “System.Net.NetworkCredential”
$basedn = “”
$scope = [System.DirectoryServices.Protocols.SearchScope]::subtree
$attrlist = “*”

The differences between what I did and what mikemstech did was that I disabled SSL and set the AuthType to Anonymous. My 389 DS refused connections if I had those two items set any other way. It’s hard to tell if this was due to it being a 389 DS server, or if it was a unique customization of the environment here.

The next thing I did in my script was create a function for querying user information, however you wouldn’t have to do this.

function Unix-LDAP {
Log (“Beginning Unix-LDAP”)
clear-variable LDAPuidNumber, filter, r, re
Clear-Variable TermEmp, ADsamAccountName, FoundUnix -Scope Global
$LDAPuidNumber = $args[0]
Log (“The UID number being queried in Unix LDAP is $LDAPuidNumber.”)
Log (“The ADsamAccountName variable is currently $Global:ADsamAccountName.”)
$filter = “(uidNumber=$LDAPuidNumber)”
$r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $basedn,$filter,$scope,$attrlist
$Global:TermEmp = 0
$Global:FoundUnix = 0
$re = $c.SendRequest($r)
$Global:ADsamAccountName = $re.entries.attributes.uid[0]
If ($Error[0] -like “Cannot index into a null array.*”) {
Log (“ERROR: The user no longer has a Unix ID. Likely a terminated employee.”)
$Global:TermEmp = 1
Else {
Log (“The Unix-LDAP query returned a value of $Global:ADsamAccountName.”)
$Global:FoundUnix = 1
Log (“Finished Unix-LDAP”)

So the important lines from above are lines 8, 9, 14, and 15. In my case I was query users based upon their uidNumber, however you could replace uidNumber in line 8 with any other property you wanted to query on.

On line 14 you’ll see that at the end of $re.entries.attributes.uid I added [0]. This was something not mentioned on the site I linked to above that I ended up figuring out. Once again, I’m not sure if this is just a difference in how 389 DS operates, or a special configuration in my environment.

You’ll also see that I work with the $Error variable. I do this so that I can determine if the query succeeded or not.

Hopefully the above will save someone else out there a bunch of time.

Migrating from LCS 2005 to Lync 2010

Beginning around March of this year I took on the project of upgrading the environment where I work from LCS 2005 to Lync 2010. There had been a project to upgrade the LCS environment for some time, however since it hadn’t been broken no one had ever gotten around to doing it. Finally though some features of Communicator 2005 broke during the upgrade to Windows 7, so LCS had to be upgraded.

I figured I would document the whole project so that I could refer back to it myself, and perhaps assist anyone else out there looking to upgrade with a better inside view to the whole thing. Note that everything below is for a “simple” environment. It’s only used for internal IM/Collaboration purposes. Clearly the steps below can’t be followed step by step if you have a more complex setup.

Now, in all seriousness, the whole upgrade from LCS to Lync with no loss of user data could’ve occurred within a month or two. However, due to the way things work in a large business, it’s dragged on into August, and will likely drag on a couple more months. Due to the amount of information that I’m going to attempt to shove into this post, I’ll break the post down into the following sections. There will be a “Preparing LCS” section, a “Preparing for OCS 2007 R2” section, an “Installing OCS 2007 R2” section, a “Decommissioning LCS” section, a “Preparing for Lync 2010 section”, a “Installing Lync 2010” section, a moving users from 2005 client to Lync 2010 client, and finally a “Decommissioning OCS” section.


Preparing LCS 2005

So prior to doing any upgrading to LCS, you have to run through a number of things to prepare it for co-existence with OCS 2007 R2. That’s right, there’s no straight shoot from LCS 2005 to Lync 2010, you have to take a pit stop through OCS first. Don’t worry, it’s really not that big of a deal, and if you follow the steps below, your users will only see the Communicator 2005 client they’re using now and the Lync 2010 client at the end. They won’t even know you took a pit stop on OCS.

So here’s a list of the things that need done to LCS first. I’ll describe them in more detail in a moment.

So starting from the beginning of the list…

You have to first install KB911996 on your back end database server and front end servers so that they can handle having your settings in the configuration container in AD. (You’ll be moving your settings after this.) You need to first install this update on your database servers. Run “LCS2005-KB911996-x86-Usa.msi POOLNAME=poolname” on the SQL server, and then you’re done with that. You do not need to reboot your SQL server. You’ll then just need to run the MSI install on your LCS front end servers.

Once KB911996 is deployed, it’s time to run the Global Settings Migration. Go to the site specified above and download the “Microsoft Office Communications Server 2007 Global Settings Migration Tool”. You’ll get an MSI, however when you run it you’ll end up having a vbs script. Below are the steps for running the “MigrateOcsGlobalSettings.vbs” script. I would run these from a LCS 2005 front end server with a user account that’s a member of the Domain Admins group. It’s possible you could get away with less permissions, but I didn’t bother with it.

  1. Run “MigrateOCSGlobalSettings.vbs /Action:MigrateGlobalSettingsTree”
  2. Replicate AD
  3. Run “MigrateOCSGlobalSettings.vbs /Action:MigrateGlobalSettingsProperties”
  4. Replicate AD
  5. From the directory “C:Program FilesCommon FilesMicrosoft LC 2005” run “LcsCmd /Forest /Action:ForestPrep /Global:configuration”
  6. From the directory “C:Program FilesCommon FilesMicrosoft LC 2005″ run LcsCmd /Domain /Action:DomainPrep”
  7. Replicate AD
  8. Run “MigrateOCSGlobalSettings.vbs /Action:MigrateServerDnReferences /SearchBaseDN:DC=domain,DC=local” (Replace the SearchBaseDN parameters with information on your domain…)
  9. Run “MigrateOCSGlobalSettings.vbs /Action:MigrateUserDnReferences /SearchBaseDN:DC=domain,DC=local” (Once again, replace the SearchBaseDN parameters with information on your domain.) You might get this step failing a couple of times. Just keep re-running this until it succeeds.
  10. Reboot LCS 2005 Front End Server(s)

Now you need to install KB921543 on your LCS 2005 front end server(s). For some reason my documentation lacks the exact reason this patch needs installed, so I’ll come back and update this section if I find exactly why it needs installed. No reboot is needed after installing this patch.

Now you need to install KB950614 on your LCS 2005 front end server(s). This patch will allow Communicator 2005 clients with KB949280 installed to communicate properly.

Finally, you need to install KB949280 on your Communicator 2005 clients. This update allows for interoperability with OCS 2007 R2. You only need this patch if your Communicator 2005 clients are running on a lower build than 1.0.559.218.

After all of that, we’re done with preparing LCS and can move onto the preparing for OCS phase.


Preparing of OCS 2007 R2

There are three main sections for preparing for OCS 2007 R2. Those are

  • Preparing AD
  • Setting up SQL
  • Setting up a OCS front end server

Before any of those steps, I would start with creating a service account for OCS. The service account I created was a member of the following security groups.

  • Domain Admins
  • RTCComponentUniversalServices
  • RTCHSUniversalServices
  • RTCUniversalGuestAccessGroup
  • RTCUniversalServerAdmins

Note that it’s possible that you need to have this service account setup with local administrative permissions on the OCS server and the SQL back end server. In my case, my service account remained a Domain Admin throughout the upgrade, which made it a local admin on the servers.

You should now prepare AD for OCS. Launch the OCS installation media and first run the ‘Update Schema Wizard”. Be sure to run this step with a user account that’s a member of the Schema Admins group, and from a computer that has AD tools installed. Replicate AD after doing this.

You’ll then need to run the “Prepare Forest” wizard. If you’re running this on a Server 2008+ system or a Windows Vista+ system then you might have to run this using lcscmd.exe instead, and running that as an Administrator. After this is done, replicate AD.

Now run the “Prepare Domain” wizard. Once again, replicate AD after this.

You’re now done with preparing AD for OCS and can move onto setting up SQL.

When it comes to setting up your back end SQL server, it’s pretty simple. You need to first make sure that if you have SQL instances available, that you do not use one that’s already hosting a LCS/OCS environment, since some database names are re-used. Personally I would recommend just create a SQL instance for OCS. Either way, once you have your SQL instance you need to configure your OCS service account with full access to the instance. (Your SQL server needs to be at least SQL 2005 SP2+. You can use SQL 2008, 2008 R2.)

You now need to setup your OCS front end server. The steps below will guide you through setting up a Server 2008 R2 SP1 system for the OCS front end roles.

  1. Install Server 2008 R2 SP1, install all available patches.
  2. Install Windows Media Runtime
    c:windowssystem32dism.exe /online /add-package
    /ignorecheck /NoRestart /quie
  3. Install IIS
  4. Install Message Queuing
  5. Run ocsasnfix.exe
  6. Create and share the following folders
    1. MeetingContent
    2. MeetingArchive
    3. ApplicationDataStore
    4. MeetingMetaData
    5. AddressBookStore
    6. ClientUpdateStore
  7. Assign a static IP
  8. Create a Host A record in DNS with the name of whatever your OCS pool will be called and point it to the static IP of your front end server. (Assuming you’re doing a single FE server setup. Otherwise you would point that Host A record at a load balancer or something, which would then target all your FE servers.)

Now that everything has been prepared for OCS, we can move to installing OCS.


Installing OCS 2007 R2

The installation of OCS can be broken down into the following steps.

  • Run through the OCS 2007 R2 setup on your front end server
  • Install the OCS administrative console on an x86 system
  • Migrate users to your OCS pool
  • Enable Enhanced Presence for users in your OCS pool
  • Update DNS/GP

Now I’ll break down each of those sections.

First log into your OCS front end server with your OCS service account. Once logged in, launc the OCS 2007 R2 setup and run through the

  • Create Pool Wizard
  • Configure Pool Wizard
  • Install Archiving Server Role Wizard (optional)
  • Install Monitoring Server Role Wizard (optional)
  • Request and Generate Cert for your OCS pool
  • Install OCS 2007 R2 Enterprise Server
  • Install Administrative Console

All of the above is pretty simple to get through, except for potentially the step for getting your certificate. For Lync, there’s a built in wizard for doing this, however I recall OCS not having such a simple wizard for getting your certificate. Basically all you need to make sure of is that your certificate is valid for the FQDN of your pool. In my case, the certificate was issued to “ocspool1.domain.local” using the built in WebServer template. Once you get your certificate, just make sure that you’re importing the certificate at the system level and not for the user you’re logged in as. Also make sure that you install all the available updates for OCS. This will require updates being made to the back end SQL server as well. You can get away without installing these updates, however you won’t have any conferencing features. (No chatting between more than 2 people.) Plus it’s always good to be running the latest version of the software when doing production upgrades….

Once you have OCS running (which you should at this point, since with these instructions we’re simply covering how to setup a simple single FE environment, and we covered setting up the pools Host A record in the previous section…), you’ll need to first install the OCS 2007 R2 Administrative Console on a 32-bit Windows XP/Windows 7 system. This needs done so that the 32-bit OCS 2007 R2 console get interface with the 32-bit LCS environment. (This is my assumption, since I can’t find any solid documentation around this.) Whenever you need to move users from LCS to OCS, you’ll need to use the OCS console on the 32-bit system.

So now that you’re all setup there, I’m going to assume that you’ve already moved some test accounts and have verified everything is working. Assuming everything is working, you’re ready to migrate your users from LCS to OCS. This is a pretty painless step. Using the 32-bit OCS 2007 R2 console, move all your users from LCS over to OCS. When you move a user, their Communicator client will get briefly disconnected, however should automatically re-connect within about 30 seconds. All their contacts should get moved over as well. You don’t have to move all your users at once. This can be stretched out over time since users can chat with each other between environments.

Once all your users have been moved over to the OCS pool, you now need to enable enhanced presence for their accounts. From within a OCS 2007 R2 console (doesn’t matter if it’s from a 32-bit system or 64-bit system), enable enhanced presence for every user account. Now, from this point forward, be careful. If any of your users login to the Communicator 2007 R2 client, or some other 3rd party chat client that supports enhanced presence, their user account will be configured for enhanced presence. Once this happens they will no longer be able to login to Communicator 2005.

So at this point you should have a LCS environment with no users based out of it, a OCS environment with all your users based out of it, and a bunch of users still using Communicator 2005 as their chat client. Assuming that you did all your work overnight or on weekends, from a user perspective they have no idea anything is going on. Now onto decommissioning LCS.


Decommissioning LCS 2005

I’m going to be pretty brief on this section. It consists of six steps.

  • Verify all your users have been migrated to OCS
  • Update your SRV record in DNS or your GPO
  • De-activate your LCS 2005 FE server
  • De-activate your LCS Archive server (optional)
  • Remove your LCS pool
  • Shut down your LCS servers

The first step is simple. Open your LCS console and verify no users are left being hosted from LCS. In the event you leave some, it’s not too big of deal. You’ll still be able to move them to OCS, however they’ll loose their contacts.

Next, you need to update your SRV record in DNS or your GPO that configures your Communicator 2005 clients. Up until this point, they’ve been pointing to your LCS pool as the “server”, however when you decommission your LCS environment, any client still pointing to the LCS pool will stop working. So first identify whether your Communicator clients get their configuration automatically via a SRV record, or “manually” via a GPO. Either way, update both to instead specify your OCS pool. Before proceeding, make sure all clients update to using the new settings.

Assuming that all your clients are now talking with OCS, you can go through and deactivate each LCS FE server. If I recall correctly, this is as easy as right clicking each FE server in the LCS console and clicking “De-activate”.

Now, if you have an Archive server, now is the time to also de-active it as well. For compliance/legal purposes, you’ll likely want to export the archive database from your SQL server and save it in a safe place.

You’re now able to remove your LCS pool. Right click the pool in the LCS console and remove it.

At this point, you can just shut down all your LCS servers. They’re done with, and so is the SQL databases / SQL instance that went with them.


Preparing for Lync 2010

You’re now at the point where you can start preparing your environment for Lync. There are four areas that need prepared.

  • Setup a service account
  • Setup a server for the front end role
  • Setup the SQL backend
  • Prepare AD for Lync

Start with creating your Lync service account. Once it’s created, add it to the following security groups.

  • Domain Admins (Temporarily)
  • RTCUniversalServerAdminsGroup

The service account will also need to be a local administrator on your Lync servers and on the SQL back end server. As far as I can tell, this step only needs done if the Domain Admins group membership is ultimately removed.

Now that you have your service account, go ahead and setup a server to act as your Lync 2010 front end server. Install Server 2008 R2 and fully patch it. You’ll then need to do the following on that server.

  • Install .NET 3.5
  • Install Visual C++ 2008 x64
  • Install SQL Server 2008 Native Client
  • Install OCSWMIBC.msi
  • Install Silverlight
  • Disable IE ESC
  • Setup a static IP
  • Install IIS

Also, now that you have your front end server setup with a static IP, you should create a Host A record for your Lync pool and point it to your future front end server. (Assuming you’re only setting up a single front end server.)

Note as well that if you want to install the Monitoring role or the Archive role that you’ll need to setup an additional server to host those two roles, assuming that you’re installing Lync 2010 Enterprise. Based on what I’ve read, the Standard Edition allows for co-location of all the roles on a single box.

As for the SQL back end, just setup a SQL instance and configure your Lync service account to have full access. Once again, don’t configure Lync to co-exist in a SQL instance that’s already in use by OCS/LCS or another Lync pool.

Finally, run through the AD prep for Lync by running Setup from the Lync installation. You’ll need to do the following.

  • Prepare the Schema (with a user account that’s a member of the Schema Admins group)
  • Replicate AD
  • Prepare the Forest
  • Replicate AD
  • Prepare the Domain
  • Replicate AD
  • Create an OU and move all your newly created Lync security groups there (optional)
  • Add your Lync service account to the security group “CSAdministrator”

At this point you’re ready to install Lync.


Installing Lync 2010

Installing Lync 2010 is pretty straight forward. Start by launching Lync setup on your future front end server. Install Topology Builder and then build yourself your Lync topology. Once you’re satisfied with the Lync topology you’ve built, publish the topology.

You then need to go to your first (or only) front end server and launch Lync setup. Do the following.

  • Install the Local Configuration Store
  • Setup Lync Server Components (Setup will automatically install the correct components for the server you’re running setup from based on the topology you already published)
  • Request, Install SAN Certificate
  • Start Services
  • Verify services started
  • Install Lync updates

Now that Lync is installed you’re ready to merge your OCS environment and Lync environment. Merging the environments allows you to upgrade users from OCS to Lync, as well as allow for communication between the two environments.

First run the OCS Merge Utility in Topology Builder, and then publish the new topology with the OCS information. You then need to run the following command from the Lync Management Shell. “Import-CsLegacyConfiguration”

At this point you should be good to test moving a user account from OCS to Lync. Now here’s were things get a little bit complicated. Assuming you followed these instructions step by step, you never upgraded any of your users from Communicator 2005 to Communicator 2007 R2. Communicator 2005 doesn’t work with Lync. Therefore you have to upgrade a user to the Lync client at the same time you migrate their user account from the OCS server to the Lync server. Now, some people might be totally against this because of the amount of coordination this requires, but take into consideration the approach I took to it, and then consider how much of a cleaner upgrade this appears to your end users by simply upgrading clients once.


Coordinating user moves from Communicator 2005 to Lync 2010

In order for my approach to work for you, you’ll have to have an environment where for the most part people have assigned systems. And then on that assigned system there’s information either in the registry on in a text file that lists information on the owner of that system. For me, we have a key in the registry populated with a bunch of information on the user that’s assigned to the system. One of the pieces of information is the users email address, which since I use their email address as their SIP address, that makes things extra easy.

The next thing you need to do is configure some way to trigger a user account move in Lync after a users system has been upgraded to the Lync client. There’s several ways you could go about doing this, but I’m only going to cover the method I ended up using.

In my case, we wrap all our installs with a vbscript. I configured that vbscript to create a text file with the name of the users email address on a network share after the Lync client is successfully installed on a system. I then have a PowerShell script running on my Lync Archive/Monitoring server that handles the actual user move. Technically this script could go on any server as long as it has the Lync Management Shell installed on it and you’ve installed OCSWMIBC.msi. The script I’ve setup to handle the user migration is rather complex, allowing the script to fix any issues that are encountered, email special instructions to end users, logging, ect. However, the script really only needs to do the following.

  1. Monitor the network share that the text files with user information are uploaded to
  2. For each text file with user information, use the Active Directory and Lync PowerShell modules to gather additional information on the user
  3. Run “Move-CSLegacyUser” for each user
  4. Have the script sleep for 10-20 seconds between each user move. The more users that are moved at once, the slower things will get. Allowing for some time between the moves allows things to keep up.
  5. Repeat

Using this type of a setup will allow you to only have to worry about coordinating client upgrades, knowing that the user accounts are being moved automatically on the back end.


Decommissioning OCS 2007 R2

I haven’t gotten to this part in my own production environment yet since I’m still stuck on the coordinating user moves, however I can supply some basic steps based upon my testing in my Dev environment.

Based upon my notes, there are three main pieces to decommissioning OCS 2007 R2. They are

  • Updating your SIP SRV Record / GPO
  • Deactivating the OCS environment
  • Updating your Lync toplogy

Granted before you do any of the above, you should verify that you don’t have any users living in your OCS pool, and that all your workstations have been upgraded to Lync 2010.

Updating your SIP SRV Record or GPO is just like what was done when decommissioning LCS 2005. You’re simply pointing your Lync 2010 clients to your new Lync front end environment instead of the OCS front end environment. Failure to do so will result in a bunch of clients getting disconnected and not being able to re-connect.

The next thing you need to do is deactivate your OCS environment. This step actually consists of a bunch of smaller steps.

First thing you’ll want to do is check for any conference directories in your Lync environment that are tied to your OCS pool. You can do so by running “Get-CSConferenceDirectory” from the Lync Management Shell and then looking for a conference identity that has a service ID tied to your OCS pool. Assuming you find one, you can use “Remove-CsConferenceDirectory” to get rid of it.

Once you’re done there, remove your OCS archive servers and monitoring servers, then deactivate every other OCS role. Once that’s taken care of, you can remove the OCS pool from the OCS Admin Console.

Now you can begin uninstalling the OCS software from your front end server(s). I’m pretty sure this would be entirely optional, since you could likely just shut down the server and be done with it. However if you do want to uninstall the software, I’ve read that you need to do so in a particular order. Here’s the order for removing the software.

  1. Administrative Tools
  2. Conferencing Attendant
  3. Conferencing Announcement Service
  4. Response Group Service
  5. Outside Voice Control
  6. Application Host
  7. Application Sharing Server
  8. Audio/Video Conferencing SErver
  9. Web Conferencing Server
  10. Web Components Server
  11. Front End Server
  12. Core Components
  13. API Core
  14. API Speech
  15. API Windows

Once that’s taken care of, you should go into Lync Topology Builder and delete the “BackCompatSite”, then publish your topology.

That should be it. You should now be able to sit back and say you’ve upgraded an environment from LCS 2005 to Lync 2010.

Uploading photos to AD and setting them as the Windows 7 tile picture

I’ve currently been working on a project to upgrade the company I work for from LCS 2005 to Lync 2010. Now that I’ve finally got the Lync infrastructure setup and am just waiting for the testing/pilot phases to finish, I’ve had some time to tinker with some other items that have been on my list of things to do.

The first thing on the list was importing photo’s into AD and then automatically having the Windows 7 tile picture for a user change to that picture, all with straight PowerShell code. (So no 3rd party add-ons or anything.)

After hunting around for awhile, I was finally able to put together two scripts to accomplish the task.

This first script is for taking a JPG image and uploading it to the current users AD account. Clearly this type of an interface isn’t something you’d want to throw at an end user, however I’ll leave how you present it to the end users up to you. More than likely I’ll create some sort of a front end to this script using PrimalScript or something.

Function Get-FileName($initialDirectory)
[System.Reflection.Assembly]::LoadWithPartialName(“”) |  Out-Null

$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = “All files (*.*)| *.*”
$OpenFileDialog.ShowHelp = $true
$OpenFileDialog.ShowDialog() | Out-Null
} #end function Get-FileName

$username = $env:username
$jpgFile = Get-FileName -initialDirectory “C:”
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$root = $dom.GetDirectoryEntry()
$search = [System.DirectoryServices.DirectorySearcher]$root
$search.filter = “(&(objectclass=user)(objectcategory=person)(samaccountname=$username))”
$result = $search.findone()
$user = $result.GetDirectoryEntry() [byte[]]$jpg = Get-Content $jpgfile -encoding byte $user.put(“thumbnailPhoto”, $jpg)

This second script will take the photo that’s been uploaded to the current users AD account and set it as their Windows 7 tile picture. Ideally you would configure this script to run upon login. The command line to have a scheduled task run is also below.

C:WindowsSystem32WindowsPowerShellv1.0powershell.exe” -Sta -executionpolicy bypass -noprofile -noninteractive -file “C:tempScript.ps1”


$code = @”
[DllImport(“shell32.dll”, EntryPoint = “#262”, CharSet = CharSet.Unicode, PreserveSig = false)]
public static extern void SetUserTile(string username, int whatever, string picpath);

public static void ChangeUserPicture(string username, string picpath) {
SetUserTile(username, 0, picpath);

#Get current logged in user
$DomainUser = (Get-WmiObject Win32_ComputerSystem).UserName

#Check logged on user is not a local account
$computername = $env:computername
$local = $domainUser.StartsWith($computername)
If($local -eq $True){


#If domain user, then configure AD pic as tile

#get username
$Username = $env:username

#Set JPG temp file
$ImageFile = “$ENV:tempADuser.jpg”

#Find User thumbnail from AD
$root = [ADSI]”
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.filter = “(&(objectClass=User)(sAMAccountName= $Username))”
$result = $searcher.findone()

$user = $result.GetDirectoryEntry()
$ADthumbphoto = $user.thumbnailphoto

#write AD thumbnail to temp JPG file
$ADThumbphoto | Set-Content $ImageFile -Encoding byte

#Configure Tile image
Add-Type -MemberDefinition $code -NameSpace NEW -Name ChangeUserTile