Posts Tagged ‘Deployment’

Function: Get-UpdateInfo – Making It Easy for Your Users to Get the Latest Version of Your Scripts

October 10th, 2016 No comments
Download PDF

updatepromptAs a PowerShell developer, you always want your users to have the latest version of a script. It makes support a lot easier, while also making sure that users have the latest features and bug fixes. But how to encourage that? Well, for me, users of my scripts are typically not within the same environment as me. So Group Policy Objects, logon scripts, etc, aren’t a solution. Having the script automatically check for an update is much easier, and doesn’t require anything from the user1. So let’s take a look at a quick and easy method.

First, we need a repository where the update information will be held. XML is perfect for this. In this example, I created the following file, and saved it as version.xml:

<?xml version="1.0"?>
<article id="1697">
<author>Pat Richard</author>
<description>Installs all required Windows 2012/Windows 2012 R2 components & optional tools.</description>

This file can reside anywhere. A file path, a web site, wherever. I chose a website for the reasons I mentioned above. You can see the above file in action at Some key points to the file. Each article I publish going forward will have it’s own “article” node. The ID I chose to tie to it is also the ID of the article’s URL, for consistency sake. In this example, 1697 is the prereq script seen at The version value is the version of the latest general availability (“GA”) build. We’ll query that value, compare it against the version of the script running the query, and see if it’s newer. Note that there is some other info in the XML file, and that’s irrelevant to what we’re discussing here.

[xml] $xml = (New-Object System.Net.WebClient).DownloadString("")
$Ga = ($xml.catalog.article | Where-Object {$ -eq $article}).version

We supply the $article value when making the call. After that, it’s a simple comparison. In the prereq script, near the beginning, I assign a variable, $version, with a value. Let’s say it’s “3.9.55”. We compare $Ga against $Version

$Ga -gt $Version

If it’s true, we know a newer version exists. If it’s false, we know the currently running script is the latest version. In theory, we could also use this to alert of a regression in case we needed to downgrade (gasp!). So let’s put this together. We assign a variable, $xml, to the results of downloading an xml file. Then, we assign $ga to the value of “version” for the specific node within the xml file that contains the info for the article. Lastly, we do our comparison and give some output if there is an update.

[xml] $xml = (New-Object System.Net.WebClient).DownloadString("")
$Ga = ($xml.catalog.article | Where-Object {$ -eq $article}).version
if ($Ga -gt $Version){Write-Output "A new version is available!"}

Now, obviously, we can pretty this up a bit. But before we do that, let’s think of issues we could run into. The big one is making sure we have an Internet connection to use to check the XML file. As much as we can often assume there will be one, a LOT of organizations block Internet access to servers as part of their security posture. So we shouldn’t assume. We can check using the following:

[bool] $HasInternetAccess = ([Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]'{DCB00C01-570F-4A9B-8D69-199FDBA5723B}')).IsConnectedToInternet)

And then using an IF loop against $HasInternetAccess. So let’s throw this all into a function we can incorporate into our scripts and modules:

function Get-UpdateInfo {
  [CmdletBinding(SupportsShouldProcess, SupportsPaging)]
  param (
    # Article/script to check for updates
    [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string] $article
  [bool] $HasInternetAccess = ([Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]'{DCB00C01-570F-4A9B-8D69-199FDBA5723B}')).IsConnectedToInternet)
  if ($HasInternetAccess){
    [xml] $xml = (New-Object System.Net.WebClient).DownloadString("")
    $Ga = ($xml.catalog.article | Where-Object {$ -eq $article}).Version    
    if ($Ga -gt $version){
      Write-Log -Level Warn -Message "Outdated version. Version $Ga is latest version. Prompting user" -NoConsole
      $wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop
      $updatePrompt = $wshell.Popup("A new version ($ga) of the script is available. Would you like to download it?",0,"A new version is available",68)
      if ($updatePrompt -eq 6){
        Start-Process "$article"
    Write-Output "No Internet connectivity. Unable to check online for update info."
} # end function function Get-UpdateInfo

Here we incorporate a simple ComObject popup message to ask if the user wants to download the new version. Since we have assigned the GA number to $ga, we can use that in the popup text, as well, as shown in the image at the beginning of this article. If $updatePrompt is “6”, then the user clicked “Yes” on the popup, and we can take action such as opening a browser window and navigating to the articles page. Or we could download a file, or any of a number of actions. If $updatePrompt is “7”, then the user clicked “No”.

So, as you can see, it’s really not that hard to add an update checker to your scripts. When you release a new version, simply update the XML file to reflect accordingly.

Note: Take care in what kind of characters are in the XML file. Some special characters, such an ampersand (“&”), aren’t handled very well. When in doubt, open a browser window and navigate to the file.

1 – Depending on the action you require once it’s known an update is available.

One Liner: Add Trusted Root Cert Authorities to Edge Servers

September 19th, 2015 2 comments
Download PDF

Chris Hayward (@WeakestLync) wrote a great blog post with a neat & easy way to add trusted root certificates for your edge servers. Of course, everything in Lync and Skype for Business uses certificates, so ensuring you have all of the certificates is crucial for federation with other organizations.

Once I saw Chris’s method, I, of course, thought that PowerShell could do this as well. Voila, a one-liner to do it. This example uses the same list from Chris’s blog post, and suppresses the output so you can use it your provisioning scripts.

"", "", "", "", "", "", "", "", "" | ForEach-Object {Invoke-WebRequest -Uri $_ | Out-Null}

This method essentially just cycles through each item in the array, and does a web request for each. As each web request is completed, any new certificates are automatically added to the trusted root cert store. Usually, some of these already exist, so don’t be surprised if the total certificate count doesn’t increase by the same number of items in the array.

Script: Install-OfficeWebAppsLanguagePacks.ps1 – Easier Installation of Selected Language Packs

March 7th, 2015 No comments
Download PDF


I was working with a global customer lately who has datacenters in various global regions (as most global orgs do). The customer had not decided, and basically, hadn’t even thought about what language packs to install on their Office Web Apps Servers (OWAS). I suggested that there are 49 language packs including the English pack that I install by default on every OWAS server. Those languages are:

Azeri (Latin)
Bosnian (Latin)
Chinese (Simplified)
Chinese (Traditional)
Irish – Ireland
Macedonian (FYROM)
Malay (Malaysia)
Norwegian (Bokmal)
Portuguese (Brazil)
Portuguese (Portugal)
Serbian (Cyrillic)
Serbian (Latin)

The customer decided on which language packs to install.

If you’ve ever tried to install these, you know you go to the language pack download page, and pick your desired language. When the next page comes up, you notice that it’s in the language of the desired language pack. You hope you’re clicking on the right link, download the file, then run the installer, which is mostly in the desired language, and go from there. It can be somewhat confusing, but extremely repetitive – especially if you’re installing a lot of language packs. It got me thinking that this was an area ripe for automation (what area isn’t?).

Well, as I’ve mentioned on this blog before, I’m lazy (as most coders are). So I wrote this script to make my life easier, and as a result, you gain from it. Here’s what the script does:

  1. Detects which (if any) language packs are installed on the local machines. This is accomplished by looking for the correct GUID in the Uninstall branch of the registry.
  2. Displays a grid list of the language packs that are available and not already installed on the machine (see image below). You can select one or more language packs to install and click “Ok”.
  3. The script will download the language pack(s)
  4. It will mount (if they are an .img file), or extract (if an .exe).
  5. It will silently install the language pack
  6. It will clean up after itself (unmount or clean up extracted files)

Here is the selection list presented. Notice that English is not in the list as that language pack is already installed.

OWAS language pack selection

Once installation is completed, you’re left with your language packs installed and a nice little log file.

OWAS post installation

Extract the files to any folder. The script and the .csv file MUST be in the same folder. Run it by calling Install-OWASLanguagePack.ps1 and it will default to using the following path structure (which it will create if it doesn’t already exist):

Path Purpose 
c:\_Install Root working folder. Can be changed using -TargetFolder when calling the script.
c:\_Install\logs Log files from the script are stored here
c:\_Install\OWASLanguagePacks Downloaded language pack files are stored here. Folder name can be changed using -OWASLanguagePackFolder. Language packs are placed in sub folders of this folder. The sub folders match the language of the language pack.

A little bit of a rant. I *REALLY* wish the language packs were an MSI file that supported silent install instead of an .img file that must be mounted or an .exe that must be extracted, and then each called with complex syntax.


Install-OWASLanguagePacks.ps1 [[-TargetFolder]][[-OWASLanguagePackFolder]] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-Skip] [-First][<commonparameters>]


Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.




v1.0 – 03-07-2015 –


See the changelog for information on what’s changed/included in each version.

One Liner: Set-TaskbarGrouping – Configure Desktop Taskbar Grouping

February 18th, 2015 No comments
Download PDF

In One Liner: Configuring Shutdown Tracker in Windows Server I mentioned that it’s often preferable to quickly configure some server settings when building servers. As a consultant, I like to set up my server profile when building servers in a manner that’s efficient and convenient for me. One thing that drives me completely insane is the default taskbar group setting. Taskbar grouping is how Windows groups common items together on the taskbar. By default, all similar items are lumped together, i.e. all Internet Explorer windows. So to go back to an IE window could take two mouse clicks instead of one. Let’s take a look at streamlining this configuration for Server 2012 and Server 2012 R2.

Taskbar grouping has three settings. The default “always combine” mentioned previously, “combine when taskbar full” which doesn’t start grouping until there are enough items to fill the taskbar, and my favorite, “never combine”. As you can probably guess, “never combine” doesn’t group taskbar items at all. Since I usually don’t have more than 4 or 5 apps open when building servers, this suits my style.

Just like the shutdown tracker, this setting is stored in the registry. A one liner for this would look like this:

Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name TaskbarGlomLevel -Value 0

0 is the value for “always combine”, 1 for “combine when taskbar full” and 2 for “never combine”. In order for the setting to take effect, one of two things has to happen. Either log off/restart, or restart the explorer.exe process. The later can be performed by running the following:

Stop-Process -ProcessName explorer -force

If you’d like to use a function for this, we can use something like the code below in our server build script:

function Set-TaskbarGrouping {
	[CmdletBinding(SupportsShouldProcess = $True, SupportsPaging = $True, DefaultParameterSetName = "NeverCombine")]
		# Always combines similar shortcuts into groups
		[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName = "AlwaysCombine")]		
		[switch] $AlwaysCombine,
		# Combines similar shortcuts into groups only when the taskbar is full
		[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName = "CombineWhenTaskbarFull")]
		[switch] $CombineWhenTaskbarFull,
		# Never combines similar shortcuts into groups
		[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName = "NeverCombine")]
		[switch] $NeverCombine,
		# Restarts explorer in order for the grouping setting to immediately take effect. If not specified, the change will take effect after the computer is restarted
		[switch] $NoReboot
	switch ($PsCmdlet.ParameterSetName) {
		"AlwaysCombine" {
			Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name TaskbarGlomLevel -Value 0
		"CombineWhenTaskbarFull" {
			Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name TaskbarGlomLevel -Value 1
		"NeverCombine" {
			Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" -Name TaskbarGlomLevel -Value 2
	if ($NoReboot){
		Stop-Process -ProcessName explorer -force
		Write-Verbose "Change will take effect after the computer is restarted"
} # end function Set-TaskbarGrouping

I use parameter set names so that only one of the parameters can be used when the function is called. The three options are “NeverCombine” “CombineWhenTaskbarFull” and “AlwaysCombine”. But since I define the parameters in a param block, you get tab completion. So no need to even remember the options. For example:

Set-TaskbarGrouping -NeverCombine

If you also include the -NoReboot parameter when calling the function, it will restart explorer.exe to avoid the need to log off/restart.

One Liner: Configuring Shutdown Tracker in Windows Server

February 17th, 2015 1 comment
Download PDF

When you spend time building servers, there are often some minor tweaks that you use to make life easier. In many environments, Group Policy Objects (GPOs) are used to configure these settings. But in a lot of environments, that’s not the case. If you build a lot of servers, you may have some scripts to help streamline the process. I often see this being the case among consultants who are engaged to deploy a solution. If you’ve followed my blog for a while, you know that’s what I do. And I look for many ways to streamline the deployment. Many solutions I write are all about the actual deployment, whereas this particular post is about the working environment I’ll be spending time in.

One thing that always drives me nuts is the Shutdown Tracker. That’s the little dialog box that pops up when you want to restart or shutdown a server. You’re presented with a prompt to pick the reason why you’re restarting or shutting down. While this can certainly have its place in an enterprise environment, it’s not generally needed during a deployment. And it’s not likely needed in a lab environment where you might be testing various configurations and restarting often. So let’s gag that annoying prompt.

To disable Shutdown Tracker, open an elevated PowerShell prompt and enter the following one line:

Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" -Name ShutdownReasonOn -Value 0

This will take care of the problem. If you later want to enable the Shutdown Tracker, you can simply run it again, specifying a 1 for the value.

We can make this a little more flexible by creating a function to let us enable or disable as needed.

function Set-ShutdownTracker {
	[CmdletBinding(SupportsShouldProcess = $True, SupportsPaging = $True, DefaultParameterSetName = "disabled")]
		# Disable the shutdown tracker
		[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName = "disabled")]
		[switch] $Disabled,
		# Enable the shutdown tracker
		[Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, ParameterSetName = "enabled")]
		[switch] $Enabled
	switch ($PsCmdlet.ParameterSetName) {
		"enabled" {
			Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" -Name ShutdownReasonOn -Value 1
		"disabled" {
			Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" -Name ShutdownReasonOn -Value 0
} # end function Set-ShutdownTracker

And the script can be called with either the -Enabled or -Disabled parameters.

Adding the one liners or the function to your deployment scripts might make life a little easier.

Function: Set-PowerPlan – Adjust The Power Plan of a Server

February 25th, 2014 1 comment
Download PDF

Just something I worked up based on a suggestion by someone. This will change the power plan of the machine it’s run on. This can be critical if you want to ensure that the machine doesn’t go to sleep while an extended process is running. Simply run the function with the desired power plan and the change is immediate. For example:

Set-PowerPlan "High Performance"

The three power plans you can choose from are “high performance”, “balanced”, and Power Saver. That’s all there is to it.

function Set-PowerPlan {
	[CmdletBinding(SupportsShouldProcess = $True)]
	param (
		[ValidateSet("High performance", "Balanced", "Power saver")]
		[string] $PreferredPlan = "High Performance"
	Write-Verbose "Setting power plan to `"$PreferredPlan`""
	$guid = (Get-WmiObject -Class Win32_PowerPlan -Namespace root\cimv2\power -Filter "ElementName='$PreferredPlan'").InstanceID.ToString()
	$regex = [regex]"{(.*?)}$"
	$plan = $regex.Match($guid).groups[1].value 
	powercfg -S $plan
	$Output = "Power plan set to "
	$Output += "`"" + ((Get-WmiObject -Class Win32_PowerPlan -Namespace root\cimv2\power -Filter "IsActive='$True'").ElementName) + "`""
	Write-Verbose $Output
Categories: PowerShell Tags: ,

Script: New-CsLyncRoomSystem.ps1 – Easily Deploy Lync Room Systems

January 28th, 2014 4 comments
Download PDF


One of the really cool features of Lync Server 2013 is the Lync Room System. LRS is comprised of a single or dual screen system, video camera, and control unit. This system provides for a rich conferencing experience by providing HD video, touch screens with white-boarding, audio & video inputs, and more. For more information on Lync Room system, see the Product Group’s blog post. To see the systems optimized for Lync, see the catalog.

Deploying a Lync Room System involves several steps, and is outlined (albeit poorly) in the LRS Deployment Guide. I say poorly because from a PowerShell perspective, the 10 steps outlined can be combined down to about 6. Some are Exchange related, some are Active Directory related, and some are Lync related.

What I’ve done is to automate & streamline the process, add a ton of error checking, optimization, and validation. Instead of picking an Exchange server, the script will automatically find and connect to Exchange. It then performs the following tasks:

  1. Create an Exchange mailbox configured as a room resource. Additionally, the description is defined, and the company name on the account is configured (see for why this is important). The room account is enabled. You’re prompted for a password for the account, and that password must conform to the organizations’ password policy for complexity. If the mailbox already exists, which would be common in most scenarios, the script will handle it gracefully, ensuring it’s configured properly.
  2. The mailtip for the account is defined. It merely reminds users to make meeting requests a Lync meeting.
  3. Set calendar processing to AutoAccept so that when the room account is added to meetings, it will automatically accept the request.
  4. The AD account is enabled
  5. The Lync Meeting room is created, and uses the email address for the SIP address. This is important to avoid Exchange Web Services (EWS) issues.
  6. If a LineURI is defined, the meeting room is enterprise voice enabled. LineURI should be specified in E.164 format.

Any other configuration, such as conferencing policies, etc., can be set after the script runs. I’ve used this script to deploy a 70″ dual display SMART Room System.

See the assumptions section below for more info.


New-CsLyncRoomSystem.ps1 [[-Alias] ] [[-Name] ] [[-UPN] ] [[-SamAccountName] ] [[-RegistrarPool] ] [[-LineURI] ] [[-CompanyName] ] [[-ResponseText] ] [[-ResourceCapacity] ] [-DeleteSubject ] [[-EnableResponseDetails] ] [-WhatIf ] [-Confirm ] []


New-CsLyncRoomSystem.ps1 -alias nycconfroom -name "New York City Conference Room" -upn "" -registrarpool ""

The SamAccountName only needs to specified if it needs to be different than the alias.


Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.


  • The SIP address is set to match the SMTP address. This is to avoid issues where the two don’t match and Exchange Web Services (EWS) calls fail.
  • Exchange 2010 or 2013 exists in the environment
  • The user running the script has the appropriate rights in Exchange (Recipient Management or higher) and Lync (RTCUniversalUserAdmin or higher)
  • The machine that the script runs on has both the Lync and Active Directory modules installed.


v1.2 – 06-10-2014 –

v1.1 – 02-08-2014 –

v1.0 – 01-28-2014 –


See the changelog for information on what’s changed/included in each version.

Changelog: New-CsLyncRoomSystem.ps1

January 27th, 2014 No comments
Download PDF

This is the changelog for New-CsLyncRoomSystem.ps1. You will find a complete list of released versions, their dates, and the features and issues addressed in each. Please refer to the script’s main page for more information including download links, installation details, and more.

v1.2 – 06-10-2014

  1. Added -AdditionalResponse option for Set-CalendarProcessing
  2. Added -DomainController switch to every command that supports it to ensure that we don’t start getting errors due to AD replication being laggy.
  3. A warning is now shown if the user services policy that is applied to the LRS has UCS enabled
  4. ResourceCapacity option added. If defined, will set the mailbox capacity accordingly

v1.1 – 02-08-2014

  1. comment help optimized per suggestions at
  2. validation for registrar name
  3. cleaned up param block
  4. validate that FE pool is 2013, exit if not
  5. new version of Set-ModuleStatus

v1.0 – 01-28-2014

  1. Original version

Cleaning Up Removed OCS Servers Before Migrating to Lync 2013

November 12th, 2013 No comments
Download PDF

Migrating a customer from OCS 2007 R2 to Lync 2013 recently, I came across an issue that needed some extra work before I could continue.

When I opened the OCS 2007 R2 management console, I noticed a server listed under “Earlier Server Versions”.

legacy server

I verified that the server no longer existed in Active Directory or DNS. The customer confirmed that it was an OCS 2007 server that had long been removed from service. This server would likely cause issues with publishing a Lync 2013 topology since OCS 2007 isn’t supported in a Lync topology. This server needed to be removed. Unfortunately, there was also no other servers in the environment with the OCS 2007 (non R2) management tools installed. And the OCS 2007 R2 management tools can’t remove the server. This meant that the only way I could remove this server is via our friend ADSIEdit. If you’ve got this issue, follow along as I show you how to remove it. Remember, we’re deep into Active Directory internals here, so tread lightly. Read twice, delete once. And for God’s sake, have a backup of AD.

Depending on where the OCS Global Settings are in Active Directory dictates where to connect to in ADSIEdit. These settings can info can either be in the root domain System container, such as if the environment originally held LCS and/or OCS 2007 servers and the settings were never migrated, or the Configuration container, where they would be if they had been migrated, or if OCS 2007 R2 was installed in a greenfield deployment. If you’re Global Settings are in the System container, open ADSIEdit and select “Configuration” in the Select a well known Naming Context field.

config container

If your Global Settings are in the System container, as was the case for this customer, Select the “Default naming context”.

Expand the domain, then expand CN=System, then expand CN=Microsoft, then expand CN=RTC Service. Inside that, expand CN=Pools. You should see the pools and servers listed. Highlight CN=Pools on the left. On the right side, right-click on the server you wish to remove, and choose Delete.

delete server

Once that’s done, close ADSIEdit. Once AD replicates, open the OCS 2007 R2 Management Console and check. The “Earlier server versions” branch should now be empty.

OCS 2007 R2 Management Console

Script: Set-Cs2013Features.ps1 – Easily Install Prerequisites and Tools for Microsoft Lync Server 2013

February 8th, 2013 129 comments
Download PDF

Lync 2013 logo 128x128Description

This script will assist in getting servers ready for the installation of Microsoft Lync Server 2013 on Windows Server 2012 and Windows Server 2012 R2. This includes the operating system prerequisites, SQL Express (where necessary), Silverlight, and more. Some post installation options are also available, and include Microsoft tools such as the debugging tools, the Best Practices Analyzer (BPA), Connectivity Analyzer, and more. Where the script needs files available online, it will automatically download them. More options will be added as I have time, and can properly test. This includes Edge, Director, Front End, Office Web Apps, Persistent Chat, and Mediation server prerequisites, and more tools. If you have suggestions, please feel free to comment below.

In the event that the server needs to be rebooted before prereqs can continue, it will automatically restart the script and continue after you reboot and login again.

The current options are:

1. Director – Installs the OS prerequisites and SQL Express instances required to install this role.

2. Edge – Installs the OS prerequisites and SQL Express instances required for this role. It also verifies the server is not domain joined, then goes through the process of setting the primary DNS suffix (same as option 50-13). Also configures NICs to remove DNS and gateway from the internal NIC, remove dynamic registration in external NIC, and prompt to disable both lmhosts and NetBIOS over TCP/IP.

3. Front End – includes the Operating System prerequisites, Microsoft Silverlight, as well as the installation of SQL Express SP2 and creation of the various required instances. The SQL Express installs are done because Lync Server installs the RTM version by default. So installing the SP2 version saves a long update later. Note that each instance takes 3-5 minutes to install – longer on slower machines. Enterprise edition servers have two instances, RTCLocal and LyncLocal, and Standard edition servers also have the RTC instance. Choosing the Front End option will ask if it’s a Standard Edition server. This option will also prompt (if the firewall is enabled) if you’d like the required firewall exceptions created for my Get-CsConnections.ps1 script. This option will also prompt if the Lync Room System Admin Portal will be installed. If you select Yes, the ASP.NET MVC 4 for Visual Studio 2010 SP1 and Visual Web Developer 2010 SP1 prerequisite for that is installed.

4. Mediation – Installs the OS prerequisites required and the RTCLocal SQL instance.

5. Office Web App – Installs the OS prerequisites required, then installs the Office Web App binaries, and then prompts to install the English language pack, followed by the most recent cumulative update. Almost everything needed to deploy an Office Web Apps server. This option also verifies that Windows Update settings are NOT set to automatic, as that is not recommended.

6. Persistent Chat – Installs the OS prerequisites and SQL instance required for this role.

7. Lync Server 2013 Resource Kit – tools that make troubleshooting and administrating a Lync environment easier, such as Address Book config, etc.

8. Lync Server 2013 Persistent Chat Resource Kit – tools useful for Persistent Chat environments.

9. Lync Server 2013 Debugging Tools – includes the logging tools such as OCSLogger and Snooper. Helpful for troubleshooting.

10. Lync Server 2013 Stress and Performance Tool – prepare, define, and validate performance

11. Lync Server 2013 Best Practices Analyzer – this tool helps identify any issues from a best practices perspective

12. Lync Server Connectivity Analyzer – identifies any issues that may result in connectivity problems for mobility clients including the Lync Windows Store app

15. Launch Windows Update

16. SCOM Watcher Node prerequisites

17. Custom PortQryUI. PortQryUI is installed, along with a custom config file that adds Lync related options.

18. Install Microsoft Message Analyzer (formerly NetMon)

19. Add custom Scheduler simple URL – if you’d like to have a simple URL for the scheduler app, such as, this option will handle the configuration of that. Note that this option requires that the simple URL provided be in the Subject Alternative Names (SAN) list of the certificate on your Front End servers. See Understanding the Lync Web Scheduler for additional info.

20. Install SQL Server 2012 Management Studio

21. ARR (“Pirate Proxy”) prerequisites. This installs the Windows features, and downloads the Web installer. It also verifies the server is not domain joined, then goes through the process of setting the primary DNS suffix (same as option 50-13). Also configures NICs to remove DNS and gateway from the internal NIC, remove dynamic registration in external NIC, and prompt to disable both lmhosts and NetBIOS over TCP/IP.

24. Microsoft Unified Communications Managed API 4.0, Runtime (UCMA 4.0) – this is required if you’re going to run sefautil.exe from the resource kit. It’s still recommended that sefautil.exe be used on a dedicated box.

28. Configure Skype Federation. This removes the MSN Public Provider and adds the Skype Public Provider, complete with icon. Download includes the Lync-Skype Provisioning Guide. See for more info.

30. Wireshark. This downloads the installer, and two compiled macro exe files and runs them. The first installs WireShark, and the second configures WireShark for optimized Lync tracing, including the steps recommended by Matt Landis (Getting Started With Lync and Wireshark: Tips & Quirks) and Jeff Schertz (Wireshark Capture Tips). That config includes:

  1. adds Source Port (resolved) column
  2. adds Destination Port (resolved) column
  3. adds DSCP column
  4. Configures RTP protocol “Try to decode RTP outside of conversations”
  5. Configures SIP protocol for ports 5060-5068 (instead of WireShark’s default of 5060)
  6. Sets the time format to human readable format

31. Enable Photo URL option. Enables the photo URL option in the client. See for more info.

34. Lync Room System (LRS) Admin portal prerequisites.

36. Create Lync file share on local computer. This creates a file share on the local computer called “LyncShare”, and assigns the basic NTFS and share rights. This can then be added to the Lync Topology Builder.

50. Misc server config menu.

  1. Install/Update Lync Server 2013 Documentation Help
  2. Create scheduled task to automatically update PowerShell help files daily. I discuss this in Function: New-PSUpdateHelpScheduledTask – Auto Update PowerShell Help
  3. Install telnet client
  4. Disable automatic updates. The automatic updating of Lync servers isn’t recommended due to the additional manual steps that must take place. And it’s not supported at all on Office Web Apps servers.
  5. Set recovery of Lync and/or OWAS services to “restart”. See Set recovery of Lync services to “restart” for more info.
  6. Set fabric logging to circular. See Tom’s excellent article at Check your lync server windows fabric log size with PowerShell
  7. Disable Server Manager on logon. For those of you who hate that it always pops up when logging in.
  8. Upgrade to PowerShell v4.0. This is for Windows Server 2012 RTM (not R2) boxes that still have the default PowerShell v3.0 on them. Upgrading PowerShell both before and after Lync Server is installed is supported.
  9. Fix Control Panel font. Reverts the font in the Control Panel back to the original Segoe UI. See Resetting the Font in Lync Server Control Panel – Goodbye Times New Roman!
  10. Set server power plan to “High Performance”. See
  11. Open HOSTS file in notepad for editing. This is convenient on edge servers.
  12. Configure edge static routing – adds the static routes for all private address ranges to use the internal NIC. The user is prompted with a list of NICs discovered, and asked to pick which will be used for the internal connection. Once picked, the script will determine if there is already a gateway defined. If so, it will use that IP address to create the static routes. If there is no default gateway assigned, the user is prompted to enter the gateway that the static routes should use. The DNS server config is removed from the internal NIC. The gateway on the internal NIC is removed. A prompt will appear, and if accepted, lmhosts lookup is disabled on all NICs. Another prompt will appear, and if accepted, NetBIOS over TCP/IP is disabled.
  13. Configure primary DNS suffix. This prompts for a domain name, assigns it as the primary DNS suffix, then reboots. This configuration is required for edge and ARR (reverse proxy) servers.

60. Desktop shortcuts menu. This is basically an enhanced menu driven version of Create a Shutdown/Restart/Logoff Windows 8 Tile for the Start menu (PowerShell) that puts easy to reach tiles on the Start screen. The available tiles are:

  1. Logoff
  2. Restart
  3. Shutdown
  4. Windows Update
  5. Lync Server Management Shell
  6. Lync Server Deployment Wizard
  7. Lync Server Control Panel
  8. Exchange UM Integration Utility (OcsUmUtil)
  9. Snooper
  10. OCSLogger Logging Tool
  11. Lync Server Topology Builder
  12. Certificate Management (local machine)
  13. Active Directory Users and Computers (ADUC)
  14. Microsoft Message Analyzer
  15. Notepad Desktop Shortcut for Edge servers to open the HOSTS file

70. Taskbar shortcuts menu. These options create shortcuts on the taskbar for commonly used tools.

  1. Lync Server Management Shell
  2. Lync Server Deployment Wizard
  3. Lync Server Control panel
  4. Exchange UM Integration Utility (OcsUmUtil)
  5. Snooper
  6. OCSLogger Logging Tool
  7. Lync Server Topology Builder
  8. REMOVE shortcut for PowerShell
  9. Certificate Management (local machine)
  10. Active Directory Users and Computers (ADUC)
  11. Microsoft Message Analyzer
  12. REMOVE Windows App Store shortcut (Windows Server 2012 R2)

80. Downloads only menu. This menu shows options for download (only) of some key Lync related products.

  1. 1. Download latest Lync Server 2013 Cumulative Update
  2. Lync Server 2013 Watcher Node
  3. Lync Server 2013 Management Pack & Documentation
  4. Lync 2013 Rollout and Adoption Success Kit (RASK)
  5. Lync Server SDN API 2.1 (includes the API installer, the management utility, the docs, and the .chm file)
  6. Lync Online Admin components
  7. Event Zero connector

90. Security menu. This menu has a few related security options

  1. Disable SSL 2.0
  2. Disable SSL 3.0
  3. EnableSessionTicket: Event IDs 32402, 61045 are logged in Lync Server 2013 Front End servers that are installed on Windows Server 2012 R2 (KB 2901554)

Simply choose your desired option. When the script is finished, it will return to the menu.

Note: The installation of some Lync Server 2013 roles requires some .Net 3.5 components, which are not installed in Windows Server 2012 by default. So the script will need to know where your Server 2012 installation media is. The script defaults to the CD-ROM/DVD-ROM drive with the lowest drive letter (typically D: or E:), but can be configured for other locations.

The script will also create a log file that can be used for troubleshooting. The log file is created in a logs folder inside the $TargetFolder (by default, c:\_install). This log file should be included when reporting any bugs.


C:\Set-Cs2013Features.ps1 [-TargetFolder ] [-Win2012Source ]
[-SQLPath ] [-InitialMenuOption ] [-IncludeSSMS] [-IncludeTelnet] [-IncludeFW] [-IncludeHighPower]
[-IncludeStandard] [-DownloadOnly] [-Tail] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-ClearRunningStatus] []



This will launch the script with the default options for Enterprise edition servers

Set-Cs2013Features.ps1 -Win2012Source e:

This will launch the script using the e: drive for the source of the Windows Server 2012 installation files

Set-Cs2013Features.ps1 -sqlpath "d:\sqlexpress"

This will install any related SQL Express instances to the specified path



Defines the location for any downloaded files. Defaults to “c:\_install”. Additionally, log files generated by this script are located in a subfolder of TargetFolder called “logs”. TargetFolder does not support paths with spaces. UNC paths are acceptable provided they are not hidden, such as \\server\share$.

-Win2012Source <String>

Defines the location of the Windows Server 2012 installation files. This is needed to install .Net 3.5 since those files are not installed on the server by default. Defaults to first detected CD-ROM/DVD drive. UNC paths are acceptable provided they are not hidden, such as \\server\share$. Unmounted .ISO images are also supported.


Defines the desired installation path for SQL Express. Defaults to “c:\Program Files\Microsoft SQL Server”


Allows you to start the script with the option you want, without first displaying the menu.

-IncludeSSMS []

If specified, will include SQL Server Management Studio automatically when prereqs for Front End servers are installed. If not specified, a prompt will appear.

-IncludeTelnet []

If specified, will include Telnet automatically when prereqs for Front End servers, Director servers, Mediation servers, Edge servers, and/or Persistent Chat servers are installed. If not specified, a prompt will appear.

-IncludeFW []

If specified, will include the firewall rules for Get-CsConnections automatically when prereqs for Front End servers are installed. If not specified, a prompt will appear.

-IncludeHighPower []

If specified, tells the script to automatically set the Power Config on the server to High Power. This is instead of the script prompting.

-IncludeStandard []

If specified, tells the script to include the extra SQL Express instance required for Standard Edition front end servers. This is instead of the script prompting.

-GetInfoFromRegistry []

This value is only used during mid-prereq reboots. It is automatically set and read by the script, and should not be manually specified.

-DownloadOnly []

Tells this script to not install or configure anything – just download the files for the option you select. This is useful if you’re going to be building servers that do not have Internet access and want to fetch the files beforehand.

-DownloadAll []

Tells this script to not install or configure anything – just download ALL of the files. This is useful if you’re going to be building servers that do not have Internet access and want to fetch the files beforehand. Note that a complete set of files is currently around 3.8GB.


 Shows a tail of the log file as it’s written. It automatically restarts if the script reboots the server, too. Really only beneficial for troubleshooting.


Resets the warning flag if the script didn’t close gracefully and you get the “The script is already running” error.



An issue has been identified in Windows Server 2012 servers that are built as Server Core, but converted later to Server with GUI. Installation of Windows Features, either manually or via a script, fail if Windows Updates are installed BEFOREHAND. That being the case, this script cannot be used in such scenarios. I’m working on detecting (if possible) servers that are converted, as well as researching why they fail. Thanks to John for pointing it out. It’s likely that the issue detailed here is the cause.

Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

Frequently Asked Questions

Question: Why doesn’t this script support Windows Server 2008 R2?

Answer: I get asked this all the time. There are several reasons. The first is that out of the box, Server 2008 R2 has PowerShell 2.0 installed, and this script is written in PowerShell 3.0. Requiring you to upgrade to PowerShell 3.0 first, before running a script that installs prerequisites, seems counter-intuitive. And converting the script to just use PowerShell 2.0 is taking a step backwards, especially considering that the current version of PowerShell is 4.0, and even as this is written, 5.0 is in preview.

Next is sheer time. I test changes I make. And then I test them again. And then I choose different options and combinations and test them. Testing on just Server 2012 and Server 2012 is exhausting. Adding Server 2008 R2 would mean even more testing, plus I’d have to add those resources in my already overtaxed test labs. That would slow down my ability to add new features and test fixes.

Third is that Server 2008 R2 is two versions back. Get with the times already!

Question: Can you add feature x?

Answer: I LOVE getting feature requests. Seriously! Best method to suggest features is to send me an email. My email address is in the comment section at the top of every script I release. Please be detailed in what you’d like to see, as well as any scenarios you’d use the option (so I can try to duplicate testing).

Question: How do I submit bug reports?

Answer: Email is best. Grab my email address from the comment section at the top of the script. Please be VERY detailed. Please include screen shots if possible, and ALWAYS include the log file (by default, it’s in c:\_install\logs). If you’re not using the latest version of the script, please download it and see if you can duplicate the problem before reporting it.

Question: What if my server doesn’t have Internet access?

Answer: Fear not. Download the required files using either the -DownloadOnly or -DownloadAll options from another machine and place them in the TargetFolder, which is c:\_install by default. The script looks to see if the file is available locally before attempting to download.

Question: When I run the script again, I get “Script already running”

Answer: This is because the script didn’t exit gracefully. Many reasons this can happen, such as rebooting the server while it’s still running. If you’re positive it’s not running anywhere else (including by other users logged into the same server), run the script with the -ClearRunningStatus switch to clear that flag. Then run it as normal.


v3.8 – 03-25-2015 –

v3.7 – 02-27-2015 –

v3.6 – 02-12-2015 –

v3.5 – 02-02-2015 –

v3.4 – 01-26-2015 –

v3.3 – 01-07-2015 –

v3.2 – 12-22-2014 –

v3.1 – 10-24-2014 –

v3.0 – 10-06-2014 –

v2.9 – 09-22-2014 –

v2.8 – 08-13-2014 –

v2.7 – 06-26-2014 –

v2.6 – 06-10-2014 –

v2.5 – 05-24-2014 –

v2.4 – 04-29-2014 –

v2.3 – 02-08-2014 –

v2.2 – 01-20-2014 –

v2.1 – 12-17-2013 –

v2.0 – 11-26-2013 –

v1.9 – 10-28-2013 –

v1.8 – 08-01-2013 –

v1.7 – 05-31-2013 –

v1.6 – 05-24-2013 – Set-Cs2013Features.v1.6.z1p

v1.5 – 05-10-2013 –

v1.4 – 05-03-2013 –

v1.3 – 04-29-2013 –

v1.2 – 04-01-2013 –

v1.1 – 02-28-2013 –

v1.0 – 02-08-2013 –


See the changelog for information on what’s changed/included in each version.