Writing a Book – A Labor of Love

October 5th, 2016 1 comment

book-coverAny tech types who’ve written tech books can attest to the fact that it’s a LOT of work. And this one was no different. Skype for Business is a very dynamic product, with features being added and updated on a continuing basis. Fortunately, I had the chance to work with some great tech luminaries – people far smarter than me, for Skype for Business Unleashed. That includes Phil Sharp, Rui Maximo, and Alex Lewis. But don’t let the fact that there are four names on the cover fool you. Plenty of others work behind the scenes, including contributing authors, editors, and publisher staff. I can’t possibly name them all, but I would like to point out a few. Stale Hansen stepped up and wrote a killer chapter on the VDI components of Skype for Business, while John Cook handled, what else, the Mac client chapter. Tom Morgan, one of Modality Systems’ ace developers, wrote on Developing Skype for Business Solutions. Former colleagues Tom Arbuthnot and Iain Smith also contributed. Even ‘The Hoff’ himself, Ken Lasko, added some great info. And to keep us all true to the product, Tim Harrington served as the tech editor. Jamie Stark, a beloved Program Manager in the Skype product group at Microsoft, wrote a killer forward.

During the project, several events occurred that seemed to derail the project. The publisher, Pearson, eliminated 4000 staff in a corporate downsizing. This was also around the time that Microsoft Press also underwent a significant restructuring. The project was in doubt for a while, but Pearson came back, committed to getting the book on to the shelves. Our normal full time gigs, family lives, and other interests also came into play. And unfortunately, someone involved in the book suffered a tragic loss. All of these caused the project timeline to slip. And during this time, the product group kept working on the product. Each time a Cumulative Update was released, we would have to review what had already been written to verify it still was valid, including details, screen shots, PowerShell commands, and more.

So why write this book? We certainly aren’t getting rich doing it. In fact, we’d all likely agree that you can’t survive on writing books at this pace. And time spent away from family and friends, and other interests can be tough. But seeing it on the shelf is rewarding on so many levels. It’s great to add the publication to your resume, LinkedIn profile, and more. Name recognition is always nice. But more importantly, getting the knowledge and experience into a format that can be beneficial to others is extremely personally rewarding to me. Is every little tidbit in there? Of course not. The book is 1100 pages. Decisions were made on how much space we could to allocate to each topic. Some chapters could be exponentially larger. But we tried to touch on the important stuff. Enough to get an environment properly designed, build, configured, and administered. And I think we did pretty well in that regard. And of course, as soon as we turned in the final edits, new features were released by the product group.

Books don’t sell unless people know about them. So we don our marketing hats and get on LinkedIn, Twitter, Facebook, blogs, and other online resources and let the world know it’s out there. Modality Systems was generous enough to put together a book signing event at Microsoft Ignite, and gave away some signed copies, as well. Twitter followers even started sending in pictures of where the book had been sighted, including the Microsoft Conference Store, MIT, and more. A signed copy even made its way to Gurdeep Pall‘s desk. Gurdeep is the Corporate Vice President of the Skype business unit at Microsoft, and he tweeted a selfie of himself holding the book. As I write this article, the book is the highest ranked Skype for Business book on Amazon. And that’s no easy task, as the other books were also written by some other top notch nerds like us.


Book signing event at Microsoft Ignite 2016. From left to right: Stale Hansen, Phil Sharp, me, Rui Maximo, and Tom Morgan.

I again want to thank everyone involved. It would not have been possible without them. I’d also like to thank the entire Product Group, as well as the Skype for Business MVPs. Both of these groups were instrumental in answering questions that popped up throughout this process.

I hope you enjoy the book, and welcome any comments or concerns.

One Liner: Enabling Mapped Drives in Elevated PowerShell Sessions

July 18th, 2016 No comments

If you’ve worked with mapped drives in PowerShell sessions, you know it’s problematic to access mapped drives from an elevated PowerShell session when UAC is configured to prompt to prompt for credentials. Microsoft released a TechNet KB article on this issue quite some time ago. The article shows different ways to address the problem, from using the Local Security Policy, mapping the drives again in the elevated prompt, and using the registry. We’ll focus on the registry here for several reasons. The first is that using the local security policy seems burdensome; mapping the drives again seems redundant, and potentially confusing if the original mappings change and the ones in your PowerShell session don’t; and thirdly, and most important, we’re talking PowerShell here!

The local security policy really just changes registry settings under HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System, so using PowerShell to set registry settings accomplishes the same thing. We can add new property, EnableLinkedConnections using the New-ItemProperty cmdlet, which also lets us set its value to 1. A value of 1 will enable the mapped drives in elevated session, while a value of 0, or completely removing the property, disables those mapped drives in an elevated session. So let’s implement this:

New-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections -Value 1 -PropertyType "DWord"

Now, if you want to put this in your PowerShell profile, then it will get processed every time. The problem is that you’ll get a “The property already exists” exception error every time it runs after the first time. So, we just wrap it in an IF statement using Get-ItemProperty, checking to see if it exists first. If not, create the item property.

if (-not (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections)){
  New-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections -Value 1 -PropertyType "DWord"

Why would we want to include this in our profile? Because when we get new machines, or reload an existing machine, we don’t want to have to go back and manually configure everything again. We can just manually run the profile script and have it configure everything for us.

Categories: PowerShell Tags: , ,

Easily Configuring Your PowerShell Profile on Multiple Machines

July 15th, 2016 No comments


I mentioned in One Liner: Enable Windows Explorer Preview of PowerShell Files that I use the same PowerShell profile script on all of my machines, courtesy of OneDrive. I wanted to show a couple of lines that are at the top of my profile that make this even easier. When I reload a computer, or get a new one, I need to configure that machine to use the shared profile. This is super easy. The profile .ps1 file is just dot sourced. So the file resides in my OneDrive hierarchy. I merely wait for OneDrive to finish its initial sync, and then open an elevated PowerShell session and run the shared file. At the top of the file is the following code:

if (-not (Test-Path $profile)){
	New-Item -Path $profile -Type file -Force
	$MyName = $MyInvocation.MyCommand.Definition
	Add-Content -Path $profile -value ". `".\$MyName`""

For information on the various files that can be used for a PowerShell profile, see Windows PowerShell Profiles. Since, by default, no profile exists, the top line in the code above, which verifies that a profile does not exist, passes. The next line creates the empty file. The third line gets the path and name of script file running (the one in OneDrive), and the fourth line adds that path as a dot source to the newly created profile. So, when you look in the newly created profile file, It has a single line:

. "d:\OneDrive\PowerShell\profile\profile.ps1"

So, when PowerShell is opened, and the profile is evaluated, the dot sourced file is loaded. Easy peasy! And, since it’s in OneDrive, all of the hard work you put into your profile is safe and secure in the event of a computer problem. But more importantly, when working from a different machine, you still have the same experience.

Feel free to comment below, including ideas, suggestions, and code sample for things you’ve done, or would like to see.


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.


Categories: PowerShell Tags:

One Liner: Enable Windows Explorer preview of PowerShell Files

July 14th, 2016 No comments

I really like the Preview Pane in Windows Explorer. It saves me from having to open files in their default app. The problem is that PowerShell files are not visible in Windows Explorer by default. And if you’re like me, you probably often look for code snippets in your .ps1 files. That’s pretty easy to fix, however, with a single line entered in an elevated PowerShell session utilizing the Set-ItemProperty cmdlet:

Set-ItemProperty Registry::HKEY_CLASSES_ROOT\.ps1 -Name PerceivedType -Value text

What this does is tell Windows Explorer to treat .ps1 files as text.

What I’ve done is add that in my PowerShell profile. My PowerShell profile is stored on OneDrive, and every machine I have references that same profile file. So, no matter what machine I’m on, I get the same experience, including the preview of PowerShell files. However, you must run it in an elevated session. So in my profile, to prevent errors, I check to see if the session is elevated, and if it is, set the property. It’s easy with:

if ((New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
  Set-ItemProperty Registry::HKEY_CLASSES_ROOT\.ps1 -Name PerceivedType -Value text

Now some of you will quickly note that only .ps1 files are included in my example. But PowerShell can also include .psm1 and .psd1 files as well. Can we still accomplish handing all three extensions in a single line of code? Absolutely. We can use Get-Item with the Include parameter to retrieve info for all three extensions, and then pipe that to Set-ItemProperty. Here’s an example:

Get-Item Registry::HKEY_CLASSES_ROOT\* -Include .ps1,.psd1,.psm1 | Set-ItemProperty -Name PerceivedType -Value text

And of course we can wrap this with the previously mentioned elevated check:

if ((New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
  Get-Item Registry::HKEY_CLASSES_ROOT\* -Include .ps1,.psd1,.psm1 | Set-ItemProperty -Name PerceivedType -Value text

I’ll be writing about more things in my PowerShell profile in the coming weeks. I welcome comments and suggestions, including things you include in your profile to help your productivity.

Categories: PowerShell Tags:

One Liner: Add Trusted Root Cert Authorities to Edge Servers

September 19th, 2015 2 comments

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.

"https://comodo.com", "https://digicert.com", "https://www.entrust.net", "https://geotrust.com", "https://www.globalsign.com", "https://godaddy.com", "https://symantec.com", "https://thawte.com", "https://wisekey.com" | 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.

One liner: Find Lync/Skype for Business Users Whose Extension Doesn’t Match Part of Their DID

September 18th, 2015 2 comments



Get-CsUser -Filter {LineURI -ne $null} | Where-Object {$_.LineURI.Split("=")[1] -NotMatch $_.LineURI.Substring($_.LineURI.Split(";")[0].Length -4,4)} | Select-Object DisplayName,LineURI | Sort-Object DisplayName

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

March 7th, 2015 No comments


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.


I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.




v1.0 – 03-07-2015 – Install-OWASLanguagePacks.v1.0.zip


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

Changelog: Install-OfficeWebAppsLanguagePacks.ps1

March 7th, 2015 No comments

This is the changelog page for Script: Install-OfficeWebAppsLanguagePacks.ps1 – Easier Installation of Selected Language Packs. 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.0 – 03-07-2015

  1. Initial version
Categories: PowerShell Tags:

One Liner: Set-TaskbarGrouping – Configure Desktop Taskbar Grouping

February 18th, 2015 No comments

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

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.