Exchange 2007 to 2013 migration

This time around I’ll write about all the steps I used for a successful Exchange 2007 to 2013 migration in a small size company (about 40 users). I’m not quite sure (yet) if we will do the upgrade to 2016 directly afterwards, however I’ll make a new 2013 to 2016 migration blog post if we decide to do so. At this point I can also state that it is impossible/not supported to do a migration from Exchange 2007 to Exchange 2016 directly, at least without 3rd party software. You can have a look at the “migration chart” at for example. If you want to pay extra money and go directly from 2007 to 2016 it should be possible with CodeTwo Exchange Migration, for example.

Anyways, onto the migration stuff itself. As always you should start with the homework. This time I don’t have that many sources for you – instead some quality ones which gets the job done properly. Start off by reading:

The second link is awesome! Read it slowly and carefully. You’ll be a lot smarter in the end. Lots of stuff to think of, but very nicely written.

I didn’t follow the guide exactly to the word (as usual), but I couldn’t have done the job without it. Some changes for our environment:

  • We do not use TMG. All TMG steps were bypassed and were replaced by similar steps according to our own firewall policies.
  • We have a Linux postfix server that handles all incoming email. It also handles antivirus and spam checking of emails. After these checks are done, it forwards email to the Exchange server.
  • Storage configuration / Hard drive partitions / Databases weren’t created the same way as in the guide.
  • Certificates were renewed by our “certificate guy”. No need for complicated requests etc.
  • No stress tests and/or analyses were done. No need.
  • Configured recipient filtering (there’s a chapter about it).
  • A script which deletes old IIS and Exchange logs was introduced (there’s a part written about this also).


My own steps for the migration process:

On the old server:

  • Patched Exchange Server 2007. Also installed the latest Update Rollup (21). You should have a fully patched (old)server before installing/introducing a new Exchange Server in the domain.


  • Took screenshots of all current configurations (just in case). Most of the settings will migrate however. Stuff to backup are nicely documented in the above homework-link.
    • Namespace
    • Receive connectors
    • Send connectors
    • Quotas
    • Outlook Anywhere, OWA, OAB, EWS, ActiveSync settings
    • Accepted domains
    • Etc.. etc. that would be of use
  • Got a new certificate which included the new host
  • Installed the new certificate (on Exchange 2007 at first):



On the new server:

  • Installed a new server, Windows Server 2012 R2.


      • Moving on to the other prerequisites:



Moving on to the actual Exchange installation

  • Had a look at my partition table, just to check that everything looked OK. (it did):


  • The partition layout should be quite self-explanatory so I won’t comment on that. I will however tell setup to use the existing partitions. I actually resized the partitions a bit after this screenshot…
  • Once again following information from the excellent guide, I used the latest CU as installation source (NOT the installation DVD/ISO).




  • Actual installation (note paths for DB and Logs):


  • Done. Moving over to post-installation steps


Post-installation steps

  • Checking and changing the SCP. This should be done asap after the installation.


          Checking SCP.


          Changing SCP.

  • Everything looks good!
  • Next, we’ll install the new certificate on the Exchange 2013 server:


           A simple “import” will do the job.

  • Also have a look at the certificate in IIS (and change to the new one if necessary):



Following the guide you should change the authentication to NTLM:

“As Outlook Anywhere is the protocol Outlook clients will use to communicate with Exchange Server 2013, replacing MAPI/RPC within the LAN, it’s important that these settings are correct – even if you are not publishing Outlook Anywhere externally. During co-existence it’s also important to ensure that the default Authentication Method, Negotiate, is updated to NTLM to ensure client compatibility when Exchange 2013 proxies Outlook Anywhere connections to the Exchange 2007 server”.

  • Moving over to the send and receive connectors.
    • The send connector automatically “migrated” from the old server.
    • The receive connector did NOT migrate from the old server. This is because Exchange 2013 use different roles for transportation compared to 2007. 2007 included only Hub Transport, but Exchange 2013 use both Hub Transport and Frontend Transport. For those of you interested in this change, read and for example.
    • The CAS receives mail on port 25 and forwards it to the “backend” mailboxes that listens on port 2525.
    • I left the “Default Frontend servername” with its default settings:


    • …and configured a new SMTP relay-connector which has “our settings”. This connector has to be “Frontend Transport”. You cannot create a new connector as Hub Transport. You’ll be greeted by an error message if you try:


Information about this can be found at:

If you want to create a new receive connector that listen on port 25, you can do this but you have to create it using the Frontend Transport role if you have either an Exchange 2016 server or an Exchange 2013 server with both the CAS and MBX roles installed on the same server”.

All our University email (and this specific company’s email) is received via a Linux postfix server. This server handles all spam filtering and antivirus. After these checks are done, the mail is delivered/forwarded to Exchange.




After these steps were done, I continued with:

  • Configuring mailbox quotas to match those on the old server.
  • Configuring the Offline Address Book to be stored on the new server.
  • Checking the log locations – should the transport logs be moved to another location or left at the default location? I changed them so they will go to the log-partition. In the end, this is just a small percentage of all logs generated. All other non-transport logs gets filled under C:\Program Files\Microsoft\Exchange Server\V15\Logging. I’m using a PowerShell script to delete all logs older than 30 days, and the same goes for the IIS logs in C:\inetpub\logs. The script looks like this, and is run via Task Scheduler daily:

$DateToDelete = 30
$StartFolder = “C:\Program Files\Microsoft\Exchange Server\V15\Logging”
$Year = (Get-Date).Year
$Day = Get-Date
Get-ChildItem $StartFolder -Recurse -Force -ea 0 | where{!$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-$DateToDelete)} | ForEach{Add-Content -Path “Delete Log $Year.log” -Value ” $_.FullName”; Remove-Item -Path $_.FullName }
$DateToDelete = 30
$StartFolder = “e:\Logs”
$Year = (Get-Date).Year
$Day = Get-Date
Get-ChildItem $StartFolder -Recurse -Force -ea 0 | where{!$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-$DateToDelete)} | ForEach{Add-Content -Path “Delete Log $Year.log” -Value ” $_.FullName”; Remove-Item -Path $_.FullName }
$DateToDelete = 30
$StartFolder = “c:\inetpub\logs”
$Year = (Get-Date).Year
$Day = Get-Date
Get-ChildItem $StartFolder -Recurse -Force -ea 0 | where{!$_.PsIsContainer -and $_.LastWriteTime -lt (Get-Date).AddDays(-$DateToDelete)} | ForEach{Add-Content -Path “Delete Log $Year.log” -Value ” $_.FullName”; Remove-Item -Path $_.FullName }

And the command to run from task scheduler:

  • PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command “& ‘D:\pathtoyour\scripts\clearlogging.ps1′”
    • Runs daily at 03:00

As you’ve probably noticed from my Exchange installation screenshots, I already pointed the Transaction logs to a different partition in the installation phase (E:\Databases\DB1). These logs don’t need manual deletion however, they get deleted via the backup solution automatically (Veeam). The key here is that the backup software has to be Exchange aware. The other logs at e:\ are the Transport logs (E:\Logs), which are only a tiny part of the whole logging structure (C:\Program Files\Microsoft\Exchange Server\V15\Logging) in Exchange. You could leave the Transport logs in their default location though, as the above script will go through that directory also…


Recipient filtering / Stopping backscatter

As a nice bonus, Exchange 2013 can now handle recipient filtering (filter out non-existent users) properly. For more information about recipient filtering read:

The filtering CAN be done without an Exchange Edge server even though Internet will tell you otherwise. We enabled it on our postfix server following tips found on Installation on the Exchange-side on the other hand looked like this:



I also enabled Anonymous users on the “Default receive connector”:


Happy days! We can now filter out non-existent users on Exchange rather than manually on the postfix server.

I also checked that recipient filtering was active and working:


Yes, it was🙂

With all this done I now moved forward with the configuration. Again, following


Getting ready for coexistence

I’ll start off by copy/pasting some text.

“With our database settings in place and ready to go, we can start thinking about co-existence – before we do though, it’s time to make sure things work within Exchange 2013! So far we’ve got our new server up and running, but we’ve still not logged in and checked everything works as expected”. Source:

With this information in mind, I started testing according to the above link. The chapter of interest was “Testing base functionality”. All tests passed. Very nice🙂

With all tests done, and all users aware of the migration, I did the following after work hours:

    • Asked the “DNS guy” to make a CNAME record for pointing to the old server.
    • Changed all virtual directories on the old server to use the name “legacy”.
      • Things to remember:
        • No external url for Microsoft-Server-ActiveSync.
        • Autodiscover Internal URL / SCP record on both Exchange 2007 and Exchange 2013 server should point to the new server.
    • DNS records are changed to point to the new server.
      • autodiscover and the namespace –record
    • Had a look at the send connector. Everything seemed OK. (Settings were migrated from the old server). However, minor change:
      • Removed the old server from the “source servers” and added the new server. New mail should be sent from the new server (and not from the old one anymore):


    • postfix was also configured to route mail to the new server instead of the old one.
    • Done. Next in line is moving/migrating mailboxes to the new server. Yay.


Migrating mailboxes

I started out by following the guide at , more specifically the part about “Pre-Migration Test Migrations”. I moved a couple of test users and after that I sent and received mail to/from these users via Outlook and OWA. No errors were noticed, so I moved over to the real deal and started moving “real” mailboxes. Again, nothing special, I continued following the information at I did a batch of 10 users at first (users A to E) and all of them were successfully migrated:


(The remaining mailboxes were also successfully migrated).


Upgrading Exchange AD Objects

Now it was time to upgrade the AD Objects following information from



The first two objects didn’t need an upgrade, they were apparently already automatically upgraded during the migration process. The distribution group in the screenshot that needed an upgrade is a mailing list/distribution group.


Public Folders

The old environment didn’t use public folders so luckily there were no need to migrate these. I did run into some problems with Public Folders however. More information in the chapter below.



  • Everything seemed fine, BUT after a couple of days one user didn’t see any new mail in a delegated mailbox she had. She also got the dreaded password prompt every time she started Outlook.
    • Later I heard that also other users were prompted for password
  • This got me thinking about authentication methods. I’ve seen this before. A couple hours of googling still had my thoughts in the same direction, authentication methods.
  • I still wonder why all of this happened though, knowing that ALL mailboxes were now hosted on the new Exchange 2013 server. Why on earth would someone’s Outlook even check for things on the old server? Maybe some old Public Folder references etc. perhaps? Don’t know, the only thing I do know is that it had to be fixed.

Some links about the same dilemma (almost, at least): (L. Authentication Issue)

The thing is, I had authentication set to “NTLM” on the new Exchange 2013 server during the coexistence, following the very same guide as with almost everything else in this post. The NTLM setting should be “enough” afaik. One thing that wasn’t mentioned in the guide however, was how the old server was/should be configured. I’m quite sure there are many best practices for Exchange 2007 also, but I myself hadn’t installed that server in the past. Well, hours later, comparing different authentication methods, I finally think I got it right. Here’s the before and after:


Before: old server IISAuthenticationMethods were only Basic.


Solution: Adding NTLM to IISAuthenticationMethods (on the legacy server)


After: NTLM added

I also removed the “Allow SSL offloading” from the new server for consistency. Not that I know if it helped fixing the problem or not.


You get kinda tired from all testing and googling, but hey, at least its working as it should and users aren’t complaining anymore!🙂


  • Shared mailbox dilemma. When you send a message from the shared mailbox, the sent message goes into your own Sent Items folder instead of the shared mailbox sent items.

If the shared mailbox is on Exchange 2010 and only has the Exchange 2010 sent items behavior configured, the settings are not converted to the equivalent Exchange 2013 settings during migration. You will need to manually apply the Exchange 2013 sent items configuration. It is probably best to do that before moving the mailbox. The Exchange 2010 settings are retained though”. Source:

    • Well, no wonder my settings didn’t stick when migrating from 2007 to 2013. I configured the correct settings again:
      • Get-Mailbox mysharedmailbox | Set-Mailbox -MessageCopyForSentAsEnabled $true -MessageCopyForSendOnBehalfEnabled $true


Decommission Exchange 2007

Still following the guide, it was now time to decommission the old Exchange 2007 server. First off I left the server turned OFF for a week. No problems were encountered, so I decided to move on with the real decommissioning work.

  • Didn’t need to touch any TMG rules (obviously, since we don’t use TMG)
  • Removed unused Offline Address Books (OAB)
  • Removed old Databases
    • Mailbox Database removal was OK.
    • Public Folders were a whole different story. What a headache. I followed almost every guide/instruction out there. Did NOT WORK. I got the “nice” message: “The public folder database “ExchangeServer\Storage Group\Public Folder Database” contains folder replicas. Before deleting the public folder database, remove the folders or move the replica to another public folder database”. God dammit. We’ve never ever been using Public Folders. Well, luckily I found some useful “fixes” after a while. Some “fixes” that MS won’t mention. Solutions:
    • Removed CN=Configuration,CN=Services, CN=Microsoft Exchange, CN={organisation name i.e First Organisation}, CN=Administrative Groups, CN={Administrative Group name}, CN=Servers, CN={servername}, CN=Information Store, CN={Storage Group Name}, CN={Public Folder Database Name} with ADSIEdit (after I had backed up the key with help from for example).
    • Ran the Get-MailboxDatabase | fl name,pub* –command again, but to my surprise the damn Public Folder Database wasn’t gone. Instead it was in the AD “Deleted Objects”. FFS, it CAN’T be this hard removing the PF Database (reference).
    • Trying to get rid of the deleted object with ldp didn’t work either: “The specified object does not exist”. I was getting even more frustrated.
    • Well, at least now according to EMC I have no active Mailbox Databases. That’s good news, so I can now remove the Storage Groups even though this annoying PF DB reference still exist in AD. I can live with it for now, and hopefully when the Tombstone Lifetime expires, so will this PF DB reference. (That wasn’t the case however, continue reading)
  • Removed Storage Groups, FINALLY:



      • System mailboxes are already on the new server. Good.
  • Uninstalled Exchange 2007 from control panel.
    • At least I tried. Of course there were problems. Again.


Got some tips from Solution was simply to start the Remote Registry service. It now uninstalled nicely.


  • Removed legacy DNS entries
  • Firewall guy was informed that the server was decommissioned and all its firewall rules could be removed.
  • Turned off the server and archived it.
  • Happy days. No more Exchange 2007.

TeamViewer on Raspberry Pi 3

During the summer we moved from an apartment to a house. Among many other things, this meant changes to the Internet connectivity. Our old place had VDSL, but now I got to choose what I want. Fiber optics would’ve been an option (fast and reliable), but it’s rather expensive compared to the other alternatives. In the end, I ended up with a 50/50 Mbit/s 4G mobile broadband connection which is shared through a 4G router (which I already owned). This was by far the most bang for the buck. We also happen to live quite near a 4G base station, so regular speeds reach about 40Mbit/s+. Not bad. Not bad at all. I’m not a gamer either, so no need for low ping times. On the negative side, all Finnish 4G mobile broadband connections are NAT:ed on the ISP side (exception is enterprise/business subscribers). This dilemma is also the subject of this post, in other words how to “bypass” dual NAT using TeamViewer. (I had to ditch my fancy old VPN stuff 😦 )

Well, moving along to the subject, TeamViewer. TeamViewer is by no means a new technology, but the possibility to use it on the RPi is limited. When googling you’ll most certainly find a solution named ExaGear Desktop. ExaGear Desktop enables x86 programs to be run (or “emulated”) on the RPi. While this is a good idea, it’ll most certainly be slow. A RPi running Raspbian is already quite slow, so doing x86 emulation on top of that is even slower. You’ll also find people telling you it’s impossible to run TeamViewer on the RPi altogether (which is untrue).

Why would you need TeamViewer on the RPi you ask? Isn’t there already SSH and port forwarding and such? Yes, BUT port forwarding won’t work with (double) NAT:ed 3G/4G connections. (NAT at the ISP level and NAT behind the router). As stated before, my main Internet connection at home is a 4G mobile broadband connection (without a public IP) connected to a 4G router. This combination doesn’t allow me to remote connect to my LAN using the “normal methods” (VPN, port forwarding and so on). The reason for this is that you can’t configure the ISP NAT rules (obviously). Some similar information about this dilemma can be found here for example: (in Finnish)


Luckily there’s a solution to this dilemma (and luckily I’m here to minimize your googling):

The TeamViewer IoT contest: 🙂

Just download and install it in Raspbian (with a simple double click). After that have a look at the Frequently Asked Questions and the General Questions & Answers + Troubleshooting Questions and Answers on the same download page. The only settings I changed were:

  • Sat an unattended fixed password and disabled random password (via the GUI).
  • Edited /boot/config.txt and commented out framebuffer_width and framebuffer_height lines. I left them at 1280×720 which worked OK for me. (I did this because I’m running the RPi headless and the resolution would therefore be screwed up (too tiny) if connecting without a monitor attached to the RPi).
  • Added the RPi to the “My Computers”-list in the TeamViewer client (from Windows)
  • Voilà!


…and some screenshots:


Fig 1. Connected, yey 🙂



Fig 2. Having a look at the doggycam remotely



Alternative solutions

  • Pay a slightly more expensive monthly fee for your mobile broadband connection, and in return get a public IP address. This is true for almost all ISP’s here in Finland at least.
  • Some ISP’s provide public IP’s on 4G mobile broadband connections using IPv6. This only works with a couple of mobile broadband sticks/routers however (they have to fully support IPv6). I wouldn’t buy a new stick (or router) only for this feature. (They’re rather expensive after all).
  • Use TeamViewer on a x86 computer instead of a RPi. Yes, why not. Then again it consumes much more energy than a RPi when in use 24/7. It’s also noisy. And hey, everything is always much more cool and hip with a RPi🙂


That’s it! This was just a short little post that hopefully will make your life a whole lot easier when dealing with the evil NAT:ed 4G connections🙂

ownCloud 9 on Raspberry Pi 2 with mounted Buffalo NAS

The Linux nerd inside me was screaming for a new RPi project. What to build? What to do? You can’t read any modern IT literature nowadays without stumbling upon the word “cloud”. Well, cloud it is. Owncloud in my case. I guess almost everyone is familiar with this open source cloud software, but for those of you that aren’t you can find information at:

The idea was to access my trusty old Buffalo NAS remotely, without having the need to map network drives etc. Buffalo is actually offering some sort of cloud solution also, but hey, it’s much more fun configuring your own stuff🙂 The idea is quite simple – The RPi is a front-end for the NAS. Clients are connecting to the RPi, which in turn mount network storage from the NAS. Here’s the setup:


Fig 1. RPi + Buffalo NAS


Initial questions and ideas

  • Should Raspberry Pi / ownCloud be visible on the Internet? If so, how to secure it properly?
      • Port forwarding with restrictions / reverse proxy?
  • If not visible on the Internet, how should one connect from the outside world?
      • VPN?

I actually decided to go with option 2, not visible on the Internet. My decision is based on the fact that I’m already running a VPN server. It’s one more extra step before getting/synchronizing the files, but I think it’s worth it in the end. Besides, all my other services are behind VPN also.

That said, I STILL configured ownCloud to be “future-proof” even if the server won’t be Internet-facing (with port forwarding etc.) right now. (See the securing ownCloud chapter). Better safe than sorry🙂



As with almost every project, I usually follow an existing guide. ownCloud is quite a mainstream product, so there are tons and tons of documentation available. The guide that I used as my baseline this time was: . Thanks to the author🙂 Steps:

  • Followed the guide down to “Now make your ownCloud directory adjust your path as necessary to your mounted hard drive folder..”. As I’ll be using a NAS, it was time for another guide:
    • created a share on the NAS (named owncloud). Gave the share read/write access for a user also named “owncloud”.
    • mounted the share on the RPi. The guide uses backslash, but it should be forward slash:
      • e.g. mount -t cifs // /mnt -o username=owncloud,password=owncloud
    • Did not follow step 3 completely because there was no data-directory created during the installation (yet). The installer GUI will look for a data directory though, so this is the time to create and mount it properly.
    • Got the uid of www-data or apache user by using id command:
      • root@owncloud:~# id www-data
        uid=33(www-data) gid=33(www-data) groups=33(www-data),1000(pi)
      • OK. ID is 33
    • Created a local data-directory which will mount the owncloud share (from the NAS).
    • mkdir -p /var/www/owncloud/data
      • changed ownership and permission on the data directory;
        • chmod -R 770 /var/www/owncloud/data ; chown -R www-data:www-data /var/www/owncloud/data
      • Added the following line to /etc/fstab (bottom of file) to make the data directory available in owncloud (setup):
        • // /var/www/owncloud/data cifs user,uid=33,rw,suid,username=owncloud,password=owncloud,file_mode=0770,dir_mode=0770,noperm 0 0
    • Ran mount –a and checked if the NAS got properly mounted. For me it did. In other words the “local” /var/www/owncloud/data was now actually living on the NAS.
    • Finished the configuration via ownClouds own GUI setup. Everything went fine…
    • …however, after a reboot the share was NOT auto mounted😦
    • I got an error when trying to access owncloud over the web interface: Please check that the data directory contains a file “.ocdata” in its root
      • Scratched my head and wondered what the hell went wrong. I was quite sure it had to do with permissions. Turned out I was right. Short version:
        • created an empty .ocdata –file (after I had manually mounted /var/www/owncloud/data directory from the NAS).
        • chmodded that file and the data-directoy with “new rights” ;
          • chmod 777 .ocdata ; chmod 777  /var/www/owncloud/data
          • success, the NAS now got automounted after a RPi-reboot🙂
    • Everything worked, so moving over to the (optional) security part.


Securing ownCloud

Owncloud’s own Security & setup warnings will warn you about things to fix. Here’s a screenshot with almost no security measurements taken. (Actual screenshot is not mine, it’s “borrowed” from the Internet):


Fig 2. Before fixing Security & Setup warnings. I’ll write about memory cache in the next chapter (Optimizing ownCloud).


… and here’s a screenshot with fixed security (also memcached fixed):


Fig 3. No security & setup warnings🙂


Basic security

The initial setup guide I followed had already done some basic security measurements (luckily):

  • Redirected all unencrypted traffic to HTTPS (in /etc/nginx/sites-available/owncloud):
      server {
        listen 80;
        return 301 https://$server_name$request_uri;  # enforce https
  • Used SSL certificates for https (self-signed):
      ssl_certificate /etc/nginx/ssl/owncloud.crt;
      ssl_certificate_key /etc/nginx/ssl/owncloud.key;
  • Created a virtual host for owncloud, not using “default”.
  • Protected the data directory and files from the internet (outside world):
       # Protecting sensitive files from the evil outside world
        location ~ ^/owncloud/(data|config|\.ht|db_structure.xml|README) {
                 deny all;
  • After this, there were still some things to take care of. Although not visible in Fig 2 above, I also got a warning saying that HTTP Strict Transport Security wasn’t used. Well, a quick googling fixed this. All that was needed was a parameter in the same configuration file as above (/etc/nginx/sites-available/owncloud):

More information about security can be found in ownClouds own Hardening and Security Guidance:


Advanced security

If you are going to deploy a server that’s facing the Internet you have to think about security. The basic security measurements are a must, but what if you want to secure it even more? You certainly want your site protected against DDOS and brute-force attacks, don’t you? Well, here’s where one of my favorites come into play – fail2ban. If you have no idea what I’m talking about I suggest that you read at least the following: (<- Specifically THIS link)

If you’re lazy, just follow the above guides and setup accordingly. However, use common sense and double check that everything seems to be in order before going production. I myself created the following jail-files and configuration files, and restarted fail2ban. Everything was working as expected🙂 My configuration files:

root@owncloud:/etc/fail2ban/filter.d#  ls -la ngin*.*
-rw-r–r– 1 root root 345 May 23 09:28 nginx-auth.conf
-rw-r–r– 1 root root 422 Mar 15  2014 nginx-http-auth.conf
-rw-r–r– 1 root root 280 May 23 09:29 nginx-login.conf
-rw-r–r– 1 root root 300 May 23 09:28 nginx-noscript.conf
-rw-r–r– 1 root root 230 May 23 09:28 nginx-proxy.conf
-rw-r–r– 1 root root 282 May 24 09:53 nginx-req-limit.conf


root@owncloud:/etc/fail2ban/filter.d# ls -la own*
-rw-r–r– 1 root root 146 May 23 09:08 owncloud.conf

(Contents of these files can be found in the links above)

…and jail-files:

root@owncloud:/etc/fail2ban# cat jail.local

enabled = true
filter  = owncloud
port    = https
bantime  = 3000
findtime = 600
maxretry = 3
logpath = /var/www/owncloud/data/owncloud.log

enabled = true
filter = nginx-auth
action = iptables-multiport[name=NoAuthFailures, port=”http,https”]
logpath = /var/log/nginx*/*error*.log
bantime = 600 # 10 minutes
maxretry = 6

enabled = true
filter = nginx-login
action = iptables-multiport[name=NoLoginFailures, port=”http,https”]
logpath = /var/log/nginx*/*access*.log
bantime = 600 # 10 minutes
maxretry = 6

enabled  = true
filter = apache-badbots
action = iptables-multiport[name=BadBots, port=”http,https”]
logpath = /var/log/nginx*/*access*.log
bantime = 86400 # 1 day
maxretry = 1

enabled = false
action = iptables-multiport[name=NoScript, port=”http,https”]
filter = nginx-noscript
logpath = /var/log/nginx*/*access*.log
maxretry = 6
bantime  = 86400 # 1 day

enabled = true
action = iptables-multiport[name=NoProxy, port=”http,https”]
filter = nginx-proxy
logpath = /var/log/nginx*/*access*.log
maxretry = 0
bantime  = 86400 # 1 day

enabled = true
filter = nginx-req-limit
action = iptables-multiport[name=ReqLimit, port=”http,https”, protocol=tcp]
logpath = /var/log/nginx/*error*.log
findtime = 600
bantime = 7200
maxretry = 10


After you’ve created the jails and the configuration files you should restart the fail2ban service, “sudo service fail2ban restart”. You can then have a look in the log file, /var/log/fail2ban.log to see if everything looks ok. For me it did:

2016-05-24 09:55:53,094 fail2ban.jail   [4778]: INFO    Jail ‘ssh’ started
2016-05-24 09:55:53,136 fail2ban.jail   [4778]: INFO    Jail ‘owncloud’ started
2016-05-24 09:55:53,162 fail2ban.jail   [4778]: INFO    Jail ‘nginx-auth’ started
2016-05-24 09:55:53,190 fail2ban.jail   [4778]: INFO    Jail ‘nginx-login’ started
2016-05-24 09:55:53,223 fail2ban.jail   [4778]: INFO    Jail ‘nginx-badbots’ started
2016-05-23 10:28:13,243 fail2ban.jail   [1350]: INFO    Jail ‘nginx-noscript’ started
2016-05-24 09:55:53,249 fail2ban.jail   [4778]: INFO    Jail ‘nginx-proxy’ started
2016-05-24 09:55:53,281 fail2ban.jail   [4778]: INFO    Jail ‘nginx-req-limit’ started

All this configuration is a bit overkill for me as I’m not going to expose the ownCloud server on the Internet. Instead I’m using VPN + ownCloud. This is however a great opportunity to learn about ownCloud and it’s security so it would be a shame NOT to configure it as secure as possible🙂 (The ssh-jail is a very nice bonus if you’re also forwarding that port towards the Internet).


Optimizing ownCloud

After all the security hardening stuff it was time to look at optimization. The initial guide includes some optimization and it installs all the PHP modules needed for memory caching. I’m quite sure I could optimize ownCloud much more, but there’s no need to overdo it in such a small home environment. In other words, memory caching is enough in my case. More info about memory caching can be found in ownClouds own documentation:

Even though this topic is already covered at the bottom in the initial guide (, I’ll write a summary here:

  • Edit /var/www/owncloud/config/config.php
  • At the bottom of the file, add:
    'memcache.local' => '\OC\Memcache\Memcached',
    'memcache.distributed' => '\OC\Memcache\Memcached',
    'memcached_servers' => 
    array (     0 => 
    array (     0 => '',     1 => 11211,     ),     ),
  • Check that memcached is running:

      root@owncloud:# netstat -nap | grep memcached
      tcp        0      0*               LISTEN      457/memcached
      udp        0      0*                           457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7961     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7955     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7753     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7967     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7960     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7954     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7966     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7964     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7958     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7963     457/memcached
      unix  3      [ ]         STREAM     CONNECTED     7957     457/memcached


  • Done. You should now have a pretty safe (and optimized) environment to play with🙂


ownCloud works pretty much the same way as Dropbox and others. Here’s a screenshot from a Mac client and one test-file synchronized:


Fig 4. ownCloud + Mac.


Useful configuration paths

I’ll finish this blog post with a short summary of useful configuration paths. Here you go:

/var/www/owncloud/config/config.php (


Adding Edge and Reverse Proxy Servers to an Existing Lync 2013 Environment

My recent task was to expand our existing Lync environment (Lync Server 2013 Standard) with an Edge and a Reverse proxy server. (This guide probably works for Skype for Business as well). Our old Lync environment had been in test usage for a while (with a rather small test-user group), but with more and more Lync users adding up it was time to expand. As a matter of fact, a Lync environment without Edge and reverse proxy servers is rather useless – you are unable to organize external meetings.

First, let me start off by saying that there are A LOT of “moving parts” involved in configuring a reverse proxy and an Edge server. You must plan for IP addresses, DMZ settings, DNS settings, certificates, firewall settings and so forth. To get a grasp of the whole picture I’m suggesting that you read/watch the following: – A very good protocol poster (Skype for Business Server 2015 Protocol Workloads) that helps with the overall picture. It’s also very good for checking firewall requirements/port configurations. Yes, it’s a bit overwhelming but very good in the end🙂 – Explains (firewall) ports.

A good place to continue after this would be It’s a very nice all-around document about Edge and Reverse proxy. Pay close attention to the chapter about Best Practices. Our goal in the end was to get something that resembles this picture:


Fig 1. Lync Front-end in combination with Edge and Reverse proxy – Simple Topology. (Pic source:


Even after a lot of reading, It’s hard to know where to start (in this blog post). There are soooooo many different things going on and a lot of stuff to remember. A good place to start could be certificate planning, which also means that you have to decide which IPs/hostnames you’ll be using in your own environment. Then again, I think the best place to start is planning network infrastructure/topology. First, consider whether you are going with a simple topology (Front-end, Edge server, Reverse proxy – we’re using this) OR a complex topology (multiple Front-ends, Edge servers, Reverse proxies). Second, It’s very important to have a working DMZ, and you should also know if you’ll be using (only) Public IP addresses or public IPs in combination with NATed ones. After you’ve got an answer to these questions it will be much easier planning for the other requirements. With this in mind, I’ll start off with the networking part. I’ll then move over to areas like DNS, certificates, actual Edge server installation, IIS ARR installation and finally some words about mobility and federation.

But first off, here’s a short explanation of what the Edge and Reverse proxy servers bring to the table:


Includes 4 modules:

  • Access Edge service. The Access Edge service provides a single, trusted connection point for both outbound and inbound Session Initiation Protocol (SIP) traffic.
  • Web Conferencing Edge service. The Web Conferencing Edge service enables external users to join meetings that are hosted on your internal Lync Server 2013 deployment.
  • A/V Edge service. The A/V Edge service makes audio, video, application sharing, and file transfer available to external users. Your users can add audio and video to meetings that include external participants, and they can communicate using audio and/or video directly with an external user in point-to-point sessions. The A/V Edge service also provides support for desktop sharing and file transfer.
  • XMPP Proxy service. The XMPP Proxy service accepts and sends extensible messaging and presence protocol (XMPP) messages to and from configured XMPP Federated partners.


Reverse proxy

The reverse proxy is required for the following:

  • To allow users to connect to meetings or dial-in conferences using simple URLs
  • To enable external users to download meeting content
  • To enable external users to expand distribution groups
  • To allow the user to obtain a user-based certificate for client certificate based authentication
  • To enable remote users to download files from the Address Book Server or to submit queries to the Address Book Web Query service
  • To enable remote users to obtain updates to client and device software
  • To enable mobile devices to automatically discover Front End Servers offering mobility services
  • To enable push notifications to mobile devices from the Office 365 or Apple push notification services




Networking / Network interfaces

I’m now assuming that you have:

  • A working Lync Server Standard/Enterprise 2013 (or Skype for Business) Front-end
  • A soon-to-become (Lync) Reverse Proxy server (Windows Server 2012 R2)
  • A soon-to-become Lync Edge server (Windows Server 2012 R2)
  • Talked to your network guys about the network infrastructure (IPs/DMZ). Hardware (F5) load balancers can be a whole different story for example.
  • Talked to your firewall guys about opening ports. I myself sat down with the Skype for Business Server 2015 Protocol Workloads printout and had a long discussion with a firewall guy. We/he got the job done without any hiccups. (It’s still working fine today🙂 )


On the Reverse Proxy:

Assign one IP for the internal network adapter and one for the external network adapter. Internal and External should be in different subnets. One interface is communicating with the internet and the other one is communicating with your internal network/AD. Have a look at or for examples. (I’m not going into much DNS details (yet), but you could name these new IPs and for example).

  • Set the default gateway on the external network adapter only
  • Assign static routes. From my experience, the information regarding this can be a bit difficult to understand. Let me copy/paste the information from the above link:

Important: Similar to the Edge Servers, you set the default gateway on the external network adapter only. The default gateway will be the IP address of the router or external facing firewall that directs traffic to the Internet. For traffic that is destined from the reverse proxy to the internal facing network adaptor, you must use persistent static routes (such as the route command in Windows Server) for all subnets containing servers referenced by the web publishing rules. Setting a persistent route does not cause the computer to become a router. If IP forwarding is not enabled, the computer is acting only to direct specific traffic destined for another network to the appropriate interface. This is essentially setting two gateways – one as the default pointing to the external networks, and one for traffic destined to the internal interface and on to a router or other network.
However, creating persistent routes for all subnets may not be necessary if your network’s routers are configured to summarize routes. Create a persistent route to the network where the router is defined and use the router as the default gateway. If you are not sure how your network is configured and need guidance on what persistent routes need to be created, consult with your company’s Network Engineers.
The reverse proxy must be able to resolve the DNS host (A) records for the internal Director or Front End Server and next hop pool FQDNs used in the web publishing rules. As with the Edge Servers, for security reasons, we recommend that you do not configure a reverse proxy to use a DNS server located in the internal network. This means you either need DNS servers in the perimeter, or you need HOSTS file entries on the reverse proxy that resolves each of these FQDNs to the internal IP address of the servers”.

In plain English this means that you configure the external interface “normally”, as you would with any other external network interface in your infrastructure. You should define the gateway as the “IP address of the router or external facing firewall that directs traffic to the Internet”. Your network guys can help you with this if unsure (also see the next chapter). The internal side on the other hand should not have a default gateway – instead you configure static routes. I’ll try to explain this:

Example network subnets (defined by your network administrators):

External DMZ (16 addresses, all are not needed but room for expansion)


Internal DMZ (16 addresses, all are not needed but room for expansion)



Server configuration:

Example external network adapter configuration on the server:


Example Internal network adapter configuration on the server:

GW:      no gw

We’re using split-brain DNS so the internal and external DNS names are the same. All IP’s are from a Class B chunk, and they’re all public IP’s that are defined as internal or external in the firewall/DNS. With the above configuration in place, you should now add a route to the internal interface on the reverse proxy server. This is done with the route add command (The –p switch make the changes persistent). Here’s an example using the above IP schema:

C:\>route add -p mask
C:\>route add -p mask
C:\>route add -p mask
C:\>route add -p mask
C:\>route add -p mask

The above command example would make all of the above IP ranges take the route against the internal interface. All other IPs would take the external route. The above ranges are also defined as internal in DNS/firewall. Do the same for all of your internal IP ranges. This method is different when using NAT and/or non-split-brain configurations. (In case of NAT, your internal IPs are in the 192.168.x.x, 10.x.x.x, or 172.16.x.x. range). Perhaps a picture will tell more than words:


Fig 2. Internal and external overview. (Picture source:

This should be it for the networking part on the Reverse proxy server. Now we do the same on the Edge server.


On the Edge server:

The network configuration on the Edge server follow the same pattern as the Reverse proxy. I’m using three external IPs and one internal IP. This is by best practice design ( If you are in a limited-budget-external-IP-dilemma, you can also make it work with one external IP (not including that option in this text however).

  • Assign three external IPs
    • one for SIP traffic
    • one for AV traffic
    • one for Web Conferencing
  • Assign one internal IP

I’m not going much into DNS details now either, but you could name these new IPs,, and for example. There’s nothing much to add here. Follow the same procedure as for the reverse proxy when configuring your internal and external network interfaces:

Server configuration:

Example Internal network adapter configuration on the server:

GW:      no gw

Example external network adapter configuration on the server:





Now add the same routes as you did on the reverse proxy. There you have it, we can now move over to the DNS part.




I assume that you by now have figured out your topology and configured networking on the involved servers. Good, that’s one step in the right direction. You might have noticed that I haven’t talked much about host names, only IP addresses. This is mostly because you can configure the networking part this far without knowing (almost) any host names. (Of course you most certainly will ask for a hostname at the same time you get an IP address, but anyways).

I have to say that DNS was one of the most confusing/difficult/challenging/painful parts in this whole configuration/deployment. There were tons and tons of misleading/wrong information, and it required countless hours of testing. Anyways, I’ll spare you the DNS-pain and tell you about our configuration in a while. But before I do, I make you read some homework. Here are a couple of interesting links (with or without errors):

Let me start off by saying that I like the jackstromberg article. All my testing was actually based on the DNS table from that article. However those records were also a bit confusing, and some even unnecessary. Here are our DNS records with comments:


Internal DNS:


Fig 3. Internal DNS

No other records are required for our specific configuration/environment (at the moment). SRV records are a thing of the past and only needed when working with Lync 2010 clients. See for more information. If you are going to use federation however (which we probably are in the future), you SHOULD set up SRV records (though not needed if manually entering servers). See: As you can see, Allowed Partner Server (Direct Federation) works without SRV records but specifying the records when you federate will probably still make your life easier.

I will now also make a statement about the record in the Internal DNS. You can read on many, MANY places on the Internet that you should have this record present in the internal DNS so that mobility works. I can confirm that our users mobile phones (WP, iOS, Android) work just FINE without this record. The key is to have the external Web Services record present in the internal DNS ( and point it to the reverse proxy. If you DO use lyncdiscover in the internal DNS, ALL traffic will go through the proxy. This is probably not a desirable configuration. Yes, I’ve seen this “live” in our environment so I know what I’m talking about. The “problem” went away after we removed the record from the internal DNS. Good info about this:

Read the above links CAREFULLY and you’ll have a MUCH better understanding, believe me🙂 Again, this setup works FOR US. I’m not saying that the record should be removed from every internal DNS configuration out there.


External DNS:


Fig 4. External DNS.

External DNS was much more straight forward. Comments are included in the picture.

The DNS records are (as you can see) a bit different for the external network/outside world compared to the internal network. All external traffic goes through the reverse proxy, which in turn use URL rewrites to connect to the corresponding URLs on the inside network. (I’ll leave the URL rewrite / ISS/ARR discussion for a later chapter).


Hosts file:

You have probably noticed that the record is present in BOTH the internal and external DNS. The reason for this is mobile devices. Mobile devices need to access the mobility service, and they do that ONLY from the outside. I’m yet again referring to the Mobility service flow using AutoDiscover (picture) at Lyncs own autodiscover feature will know if the client is on the internal or external network based on the lyncdiscover/lyncdiscoverinternal record. However, it’s a whole different story with If this URL is accessed from either the inside or outside network, the client is unable to know it’s final destination. This is because you’re pointing the client (in both cases) to the reverse proxy, which in turn point to the same URL internally and externally. This means that you’ll end up in an endless loop. To solve this you’ll edit the hosts-file.

You also have to add a local DNS record for lyncdiscover, otherwise this record will remain unresolvable as it’s not present in the internal DNS. This was all a big mystery for me, as the documentation seldom mentioned this dilemma. I got an idea after hours of googling though – the holy hosts-file. Thanks to for the idea. This was by no means a big surprise, but you’ll get lost (in DNS) after hours and hours of testing. Believe me.

This means that you’ll have to add local DNS records on the reverse proxy. Fire up notepad and edit the C:\Windows\System32\drivers\etc\hosts –file on the reverse proxy. Add the following:, where is the IP of your Front-end server., where is the IP of your Front-end server.

(Meet and dialin are already resolvable by internal DNS and correctly points to the Front-end).

Now when a client resolves webext (internally or externally), it always gets sent to the reverse proxy. The reverse proxy in turn resolves webext to the front-end via the hosts-file. Lyncdiscover in turn won’t be resolvable internally after it reaches the reverse proxy if no hosts file-record is added. There you have it – all your DNS problems solved🙂




Certificates and DNS go somewhat hand-in-hand as you need to know which hostnames you’ll be using in the certificates. I’d probably start off by reading again. Some information to get you started:

Take your time to read and plan – this way you’ll be rewarded in the end. The above links discuss both internal and external certificates. They also discuss the differences between the reverse proxy and the Lync Edge certificates.

We’re not completely going by best practice regarding the certificates. We’re using external certificates on the internal front-end server. This is due to the fact that we already had an external certificate installed on the Front-end. It doesn’t do much harm either, and at least for us it’s not an extra expense.

Without further stories I’ll present our “certificate solution”. We’re using one certificate per interface on the edge server, but you could also use just one certificate will all hosts included. This would be more expensive due to the fact that you’ll have to pay extra for additional SAN-names. (You DON’T need a certificate for the AV-interface on the Edge server).


Fig 5. Edge and Reverse proxy certificate chart.

To add to this list, our Front-end needed to get its public certificate renewed with the added host Before the renewal, it included public certs for lyncdiscover, lyncdiscoverinternal, meet, dialin and sip. Now it includes all those + webext. Webext is needed for the external Web Services (externally accessing the front-end). More about that later on.

The following chapters has information about how to install the certificates on the servers.



Configuring the Lync Front-end Server for Edge / Installing the Edge Server / Edge Server Certificate Installation

Congratulations If you’ve had the energy to read this far. It’s now finally time to install the Edge server🙂 Much of the Edge installation/configuration is actually tied to the Front-end server however. You’ll start by making changes to your current topology and then export/publish the topology on the Edge server. Like you’ve probably noticed before, it’s the prep work that takes most of the time. (Internet is full of articles on how to install an Edge server). That said, I happen to like the post at and our installation is based on this article. However, there are some differences. In Step 6, the article tells you to request internal and external certificates from the setup itself. We didn’t do it this way because our internal CA isn’t an “online certification authority” (it doesn’t respond to online web requests due to security reasons). Instead we made an offline request and signed it manually on the CA. A little bit more hassle, but worked just fine in the end.

We didn’t use the certificate request wizard for the external certificate either, as those gets created by our “certificate guy”. He uses his own methods and just delivers a fully working certificate. I won’t go into the details, but it works. So in the end, whatever floats your boat and can get you the correct certificates is fine🙂

If using this “manual method” (alternative to Step 6 in the guide), you must manually install the certificate(s) in the certificate store(s) before continuing. This is by no means difficult. For the internal/external certificates do the following:

  • Fire up “mmc.exe” on the Edge server
  • Add the certificates Snap-in. Select computer account –> local computer and click OK.
  • Right click on the Personal –> Certificates folder.
    • Select All Tasks… Import
    • Imported certificate will show up in this location.
  • Move the different certificates to their corresponding places (Personal, Trusted Root Certification Authorities, Intermediate Certification Authorities). See screenshots/figures below.


Fig 6. Personal certificate (computer)


Fig 7. Trusted Root CA certificate


Fig 8. Intermediate CA certificate

  • Done. External certificates are also showing in this list as I have imported them in the same manner.

Now, back to the installation post at

Some of my own notes:

  • Followed the guide.
    • Network specifications were OK.
    • Software specifications were OK. Didn’t (need to) install Windows Identity Foundation on the Front-end. (It should be installed as a pre-requirement on the Edge server however).
    • Fired up Topology Builder on the front-end and followed the guide.
      • FQDN of the new edge pool should match the FQDN (CN) on the internal certificate: from certificate chart
      • Access Edge service should match the FQDN (CN) on the external certificate:
      • Web Conferencing Edge service should match the FQDN (CN) on the external certificate:
      • A/V Edge service should match the FQDN (CN) on the external certificate:
        • NOTE: This is a “DNS/certificate-thing”. Whatever certificate CN-record you created for the above services should be used. You can swap CN and SAN records for a more “clean” name, i.e. vs. (See above DNS/certificate chart and the note about switching places between CN and SAN).
      • Enabled federation (not xmpp yet though)
      • Defined internal and external IP addresses
      • Defined Next hop pool: our front-end
    • Changed the External web services in the Topology builder to match the one we have defined in DNS and in the certificate (would be in the example).


Fig 9. External web services

    • Published the topology
    • Exported the configuration (step 4 in guide).
  • Moved over to the Edge server itself
    • Installed Lync server and imported the configuration (step 5 in guide)
    • The certificates were already installed in the certificate store so no need to request certificates (step 6)
      • Defined the existing certificates
    • Started services
    • Done! (no need for step 8 and 9, yet)



Installing and configuring the Reverse Proxy server / Reverse Proxy server Certificate Installation

I’m now assuming that you have a working Edge server. You can install the reverse proxy server without a working Edge server also, but installing the Edge server first makes it easier to test the reverse proxy functionality right after the installation. First some homework/reading:

I decided to go with a combination of the Microsoft guide and the jackstromberg one this time. In the end, it worked perfectly. I had lots of problems and headaches down the line, but this time it had nothing to do with the guides (rather it had to do with typos and a non-working ARR that had to be reinstalled).

Before following the (Microsoft) guide however, we have to install the certificates the same way we did on the Edge server. The reverse proxy certificates are of course different, but I’m assuming that you have requested them at the same time as the Edge certificates. Just follow the Edge steps (fire up mmc.exe and so on) and you’re good to go. One different step is that you have to bind the certificates in IIS, otherwise they won’t be used when clients connect via the reverse proxy. It’s rather easy, let me show you some screenshots:


Fig 10. Adding https bindings in IIS.


Fig 11. Adding https bindings in IIS. Remember to add both the internal and the external interface, with their own certificate.

Now continue following the guides, or use another guide of your choice.

I used the following simple URLs:


All good, tested and working!🙂

A note from my own experiences: ARR is VERY STRICT regarding the URL rewrite rules. If something isn’t working, be sure to double check the rules!




I wasn’t quite sure if there was a need for a separate mobility chapter as I’ve covered this area quite well in the DNS and certificate chapters. I guess a couple of lines won’t do no harm however. I’ll once again start by giving you a nice list of homework/reading: (deleting cache)

Much of the mobility stuff has to do with the fact that the mobility service isn’t working properly via lyncdiscoverinternal. Instead you configure the mobile devices to go the external way, via the reverse proxy. See the following picture:


Fig 12. Lync mobility (source:

The mobility bit was a big headache, but in the end we got it working in a desirable way. The secret was to remove from the internal DNS (against many recommendations). See the DNS chapter for more information.




We’re definitely interested in federation, but we haven’t federated with any partners yet. It’s no harm reading about federation though, and in the end it will be much easier setting it up once you’ve done your homework. I’ve done my homework, so why wouldn’t you🙂 Here you go:

The Edge server is already enabled for federation, but the front-end is not. This is easily fixed in the Topology builder once we/you decide to federate:


Fig 13. Enable federation on the Front-end.

In addition to this, it’s also recommended that you add a DNS SRV record (



And finally here’s a screenshot I took sometime in the middle of the whole deployment. As I’ve stated before, there were lots and lots of googling and homework to be done🙂


Fig 14. Google is your friend. Don’t believe everything you read though…

That’s Firefox with Tab Mix Plus and Multirow Bookmarks Toolbar Plus Extensions, btw.


This quite much summarizes the Lync Edge and Reverse Proxy server deployment. Hope you’ve enjoyed reading🙂

Get Programs and Features script

I was trying to find a good method/script to (remotely) get all the installed programs from a Windows 7 client. I didn’t want to use Remote Desktop or similar however, so it had to be a method that was unnoticeable for the user. I know that SCCM has its own software inventory, but I wanted a fast(er) way to check if version XXX of Office is installed on a client. The reason for this was mainly because there are (unfortunately) soooo many different Office installations throughout the whole University. (Also, it’s nice to have some clues about the client before SCCM starts using its magic). The script lists more than (Office) installations however, including other useful information as well. It is based on

and It basically queries the registry of a remote client and returns the installed software. Cheers to the author Anthony Howell!

This modified version also lists:

  • Colored headings🙂
  • OS Name
  • OS Version
  • System Model
  • Total Physical Memory
  • Original Install Date
  • Uptime/System Boot Time
  • Current logged on user

Usage: .\get_programs_and_features.ps1 computername.

Here’s a screenshot with example output:


Fig 1. get_programs_and_features.ps1


The text in the screenshot is in Swedish, but it should be quite self explanatory🙂

If you want to have a go yourself, the script looks like this (download link at the bottom):

        [string[]]$Name = $env:COMPUTERNAME
        $LMkeys = “Software\Microsoft\Windows\CurrentVersion\Uninstall”,”SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall”
        $LMtype = [Microsoft.Win32.RegistryHive]::LocalMachine
        $CUkeys = “Software\Microsoft\Windows\CurrentVersion\Uninstall”
        $CUtype = [Microsoft.Win32.RegistryHive]::CurrentUser
        ForEach($Computer in $Name)
            write-host “Information om datorn: ” $Computer -foreground “magenta”
            $datastring= systeminfo /s $Computer
            $datastring | find “OS Name”
            $datastring | find “OS Version”
            $datastring | find “System Model”           
            $datastring | find “Total Physical Memory”
            $datastring | find “Original Install Date”           
            $datastring | find “System Boot Time”
            write-host “Inloggad person för tillfället (console): ” -foreground “magenta”
            qwinsta /server:$Computer
            $MasterKeys = @()
            If(!(Test-Connection -ComputerName $Computer -count 1 -quiet))
                Write-Error -Message “Unable to contact $Computer. Please verify its network connectivity and try again.” -Category ObjectNotFound -TargetObject $Computer
            $CURegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($CUtype,$computer)
            $LMRegKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($LMtype,$computer)
            ForEach($Key in $LMkeys)
                $RegKey = $LMRegKey.OpenSubkey($key)
                If($RegKey -ne $null)
                    ForEach($subName in $RegKey.getsubkeynames())
                        foreach($sub in $RegKey.opensubkey($subName))
                            $MasterKeys += (New-Object PSObject -Property @{
                            “ComputerName” = $Computer
                            “Name” = $sub.getvalue(“displayname”)
                            “SystemComponent” = $sub.getvalue(“systemcomponent”)
                            “ParentKeyName” = $sub.getvalue(“parentkeyname”)
                            “Version” = $sub.getvalue(“DisplayVersion”)
                            “UninstallCommand” = $sub.getvalue(“UninstallString”)
            ForEach($Key in $CUKeys)
                $RegKey = $CURegKey.OpenSubkey($Key)
                If($RegKey -ne $null)
                    ForEach($subName in $RegKey.getsubkeynames())
                        foreach($sub in $RegKey.opensubkey($subName))
                            $MasterKeys += (New-Object PSObject -Property @{
                            “ComputerName” = $Computer
                            “Name” = $sub.getvalue(“displayname”)
                            “SystemComponent” = $sub.getvalue(“systemcomponent”)
                            “ParentKeyName” = $sub.getvalue(“parentkeyname”)
                            “Version” = $sub.getvalue(“DisplayVersion”)
                            “UninstallCommand” = $sub.getvalue(“UninstallString”)
            $MasterKeys = ($MasterKeys | Where {$_.Name -ne $Null -AND $_.SystemComponent -ne “1” -AND $_.ParentKeyName -eq $Null} | select Name,Version | sort Name)
            write-host “Har följande program installerade:” -foreground “magenta”


Downloadable version (translated to English):

Installing SharePoint 2013 in a two-tier topology

I got the task of installing SharePoint 2013 for a small business. The SharePoint site won’t be used by that many people simultaneously, so the server load will remain quite small. With that in mind I had to figure out a suitable topology. There are many, many sources on the web describing this, so getting information wasn’t a problem. In the end, I decided to go with a two-tier topology. A single-tier would have been sufficient, but It’s nice to have a separate SQL-server which can be used by other applications/servers as well.

“In a two-tier deployment, SharePoint 2013 components and the database are installed on separate servers. This kind of deployment maps to what is called a small farm. The front-end Web servers are on the first tier and the database server is located on the second tier. In the computer industry, the first tier is known as the Web tier. The database server is known as the database tier or database back-end”.


Another useful link: (you’ll find a nice document/pdf describing Streamlined Topologies for SharePoint 2013). The document states that a two-tier farm is sufficient for up to 10.000 users. More than enough in my case.

My installation is actually based on, even though I would call this a two-tier topology and not three. The SQL-guide from this post is not used, as it suggest installing every component (which is unnecessary). Shortly said there are only two servers included in my setup, namely:

  • SharePoint 2013 (more about features and roles later in the document)
  • SQL Server 2014 Standard

I won’t go into the hardware details of the servers themselves because it varies so much from deployment to deployment. It’s easy to scale out with more memory or better/faster SAN-disks if you have the need for it in the near future. It’s also a good idea to read the following information before installing:


AD Accounts for SharePoint and SQL

My first task was to create the needed service accounts in Active Directory. There’s a very good site describing the needed accounts at I only used

  • sp_install (SharePoint installation)
  • sp_farm (SharePoint Farm Account)
  • ​sql_install (SQL server installation account)
  • ​sql_user (SQL user account)

from the list. Later I created an account named sp_srv for running miscellaneous services. This is more than plenty for such a small deployment. You can read more about service accounts here:

SharePoint 2013 Service Accounts Best Practices Explained: (I’m using medium security option)

Initial deployment administrative and service accounts in SharePoint 2013:

SharePoint 2013: Service Accounts:


SQL Server 2014

Next on the checklist was the installation of SQL Server 2014. SQL is a requirement for SharePoint so it should be installed before you install SharePoint itself. I decided to go with as a base for my installation. Before installing, I also suggest reading the following (you can never be too prepared):

A simple install of SQL Server 2012 for SharePoint Server 2013 or 2010:

Instruction Guide for Installing SQL Server 2012 SP1 for SharePoint 2013:

Install SharePoint 2013 – Part 4 SQL Server:

Service Account Suggestions for SharePoint 2013:

“The SQL Guy” Post #15: Best Practices For Using SQL Server Service Accounts:



After doing some homework (reading articles) I came up with the idea of using SQL with a Named Instance (with SQL-aliases for SharePoint) instead of the Default Instance. I also thought of blocking the default SQL port and using a new static one (configured by SQL aliases). All of this to get better security. I buried this idea however, and instead running with the Default Instance following this guide: (The server itself is already quite well firewalled by a hardware firewall). Some more information regarding the same matter:

Best practices for SQL Server in a SharePoint Server farm:

Blocking the standard SQL Server ports:

Configure SQL Server security for SharePoint 2013 environments:

If one ever decide to use SQL aliases, it’s advisable to read the following document:

I secured the SQL server using “server isolation” instead.

“Server Isolation can be done several different ways, but the end result is the same: configuring the server to only respond to authorized machines.”


In my environment, I’m only allowing traffic from the soon-to-be installed SharePoint server (using the above method).



With the security taken care of, it’s finally time for installation! Following the guide I mentioned earlier (, I went through the steps. I got a firewall warning in the setup (Fig 1), but it was easily fixed by poking a hole in the windows firewall (Fig 2).


Fig 1. SQL Server 2014 Setup warning



Fig 2. Poking a hole in the firewall (Added the SharePoint server IP).

Next step:

  • Enabled Server Feature: .NET Framework 3.5 (needed for SQL server installation)

Continued the setup:

  • SQL Server Feature selection:
    • Database Engine Services
    • Management Tools – Complete
  • That’s it, no extra crap;

“After selecting SQL Server Feature Installation and clicking Next, a list of SQL Server features is displayed, as shown in Figure X. We really need only one SQL Server feature for SharePoint: Database Engine Services. However, I will also install the Management Tools (Complete) feature, which gives you handy tools such as SQL Server Management Studio. As you browse through the list of features, you might be tempted to check more features than you really need. But unless you’re going to use a particular feature immediately, I don’t recommend installing it. If you want to add a feature later, such as SQL Server Reporting Services, you can just run Setup again and add the feature to your existing instance.”

Source (again):

Server Configuration/Service Accounts:

  • SQL Server Agent and SQL Server Database Engine: sql_user (the AD account created earlier).

Database Engine Configuration/Specify SQL Server Administrators:

  • myadminaccount and sql_install (the AD account created earlier).

I’m using the default installation paths for SQL as this is a small scale installation.

Installation complete!



All tweaks are based on the following articles:

  • Max degree of parallelism = 1
  • Maximum server memory 3.5GB (out of 4GB)
  • Model Database’s Recovery Model: simple
  • Compressed backups
  • Also adding the sp_install user to SQL, see below:

“To give the sp_install account the permissions it needs, in SSMS navigate to Security, Logins in Object Explorer. Right-click and select New Login. Under General, type the username and make sure you include the domain. Then on the Server Roles page, shown in Figure 3, select the dbcreator and securityadmin check boxes and verify that the public check box is still selected. Then click OK.”


Fig 3. Assigning Permissions to the sp_install Account

“Let me offer a few words of advice about setting the sp_install permissions. SharePoint assumes that those three roles, dbcreator, public, and securityadmin, have the default set of permissions in SQL Server. Don’t alter those permissions. I’ve seen DBAs in very secure environments try to lock down these three roles. Doing so will most certainly break SharePoint in crazy and unusual ways. That might not happen right away, and it might not happen to you when you’re using the interface. It could be a monthly timer job that fails, for instance. Also, don’t change any SQL Server permissions that SharePoint sets. SharePoint is very fussy, and if it sets permissions, it really needs them. Because of SharePoint’s rigidity on its SQL Server permissions, I recommend that you put SharePoint in its own SQL Server instance. SharePoint will thank you, and so will your DBAs.”


That’s it for SQL, moving on to the SharePoint installation.



SharePoint Server 2013 installation

I’m being a bit lazy now and just copy/pasting information… why rewrite something that someone has already written (well)?

SharePoint Server 2013 checklist:

Before you begin to install and configure SharePoint 2013, do the following:


Everything in order, let’s continue! (Again, the installation is quite much based on

Well, I didn’t get so far. The prerequisite checker failed with the message: Application Server Role, Web Server (IIS) Role: configuration error.

A suggested solution was to install a hotfix from Microsoft; This didn’t work however, as the fix was only for Windows Server 2012, NOT the R2 version. Next test was to follow a guide from

Steps to fix (Installing .Net Framework 3.5):

  1. Insert the Windows Server 2012 installation image or DVD
  2. Open a command prompt window (run as Administrator) and run the following:
  3. Dism /online /enable-feature /featurename:NetFX3 /All /Source:D:\sources\SxS /LimitAccess


Fig 4. Success!🙂


Continuing with the setup…


Fig 5. Complete installation (production). Using default file locations (because small scale installation).

Done. The SharePoint Configuration Wizard will then run:


Fig 6. Create a new farm



Fig 7. Database settings. Database server and account settings were discussed in the SQL chapter.



Fig 8. SharePoint Central Administration Web Application

Port 18811 (or whatever SharePoint chooses for you) must be blocked (outside the domain), otherwise the Central Administration URL will be open for anyone on the Internet.



Fig 9. Completing the configuration wizard



Fig 10. Configuration successful!



There are A LOT of different services running on a SharePoint server. However, in a small scale environment, you’ll probably only need/use a few of these. I took a look at the old server and compared the services running there. Here’s a screenshot of SharePoint 2010 and its active services:


Fig 11. SharePoint 2010 services

From the screenshot we can see that the following services are running:

  • Central Administration
  • SharePoint Foundation incoming E-Mail
  • SharePoint Foundation Web Application
  • SharePoint Foundation Workflow Timer Service

With this in mind, I tried to keep the services at a minimum on the SharePoint 2013 server as well.

I couldn’t find the exact same ones in 2013, but I decided to go with the following:


Fig 12. SharePoint 2013 services

  • Search Service Application
  • State Service
  • Usage and Health data collection


After SharePoint had configured itself I was greeted with a message that some services are running with the “wrong” accounts (Fig 13).


Fig 13. SharePoint Failing Services

The failing services are:

  • SharePoint Central Administration v4 (Application Pool)
  • SPTimerV4(Windows Service) = Farm
  • AppFabricCachingService (Windows Service)


My idea was to run the default SharePoint services with the “sp_farm” account. Other services can be run with the “sp_srv” account if/when needed.

Update: It’s not recommended running the Wizard, instead you should manually configure the settings.


You change the account settings in SharePoint –> Central Administration –> Configure service accounts. I changed the farm account to “sp_farm”. Everything more or less broke after that😦 I had to do some googling to get it up running again.

Solution (before changing farm account to sp_farm):

  • Register the account (sp_farm) as a managed account. To change a managed account password go to Central Admin > Security > Configure Managed Accounts (/_admin/MangedAccounts.aspx). Click the Edit icon next to the account whose password you want to change.


           Fig 14. Register Managed Account.

  • Go to the Configure Service Accounts page and Select the Farm Account and set the new managed account
  • Reboot the server.



Done. SharePoint is now installed🙂



You shouldn’t use http with SharePoint outside your domain. Instead you should use https (http over SSL). Request a certificate for your SharePoint site from a 3rd party certificate issuer (or similar), and then apply the certificate. You could/should also use http redirection (http –> https) and/or Alternate Access Mappings. You can follow these guides for example:

Deploying Office 2016 and Office Proofing Tools Kit 2016 / Office 2016 language packs with SCCM 2012 R2

I recently deployed Office 2013 SP1 (and wrote a blog post about it). Now I got the honours to do the same for Office 2016. The procedure is theoretically quite the same, but with many different bugs and gotchas. The following procedure describes how to remove any previous versions of Office and corresponding Proofing tools & language packs and replace them with Office 2016 versions. I’ll dive right into the steps:

Customizing Office 2016

  • Got a copy of Office 2016 and extracted the iso. Our version is “Professional Plus” with Volume Licensing.
  • Fired up a command prompt and navigated to the extracted Office directory.
  • From here I ran “setup.exe /admin”  to launch the Office Customization Tool (OCT).
  • Customized the OCT settings according to our needs:
    • Setup/Organization Name
    • Setup/Licensing and user interface/Use KMS client key
    • Setup/Licensing and user interface/I accept the terms in the License Agreement, Display level: None, No tick in Competition notice, Tick for Suppress modal and No cancel.
    • Setup/Remove previous installations/Remove the following earlier versions of Microsoft Office Programs/Remove all (here’s a BUG btw, more on that later on).
    • Setup/Modify Setup Properties:
      • SETUP_REBOOT, value Never
    • Features/Modify user settings:
    • Features/Set feature installation states:
      • Everything gets installed.
    • Additional content/Configure shortcuts
      • For some weird reason, Office 2016 by default decides to put all of the shortcuts on the start menu WITHOUT a subdirectory – splattered all over the start menu. This is fixable by editing the shortcut entries. In Fig 2. below, I’ve added the path/directory \Microsoft Office 2016. You also have to add a “[“ in Start in, otherwise you can’t close the dialog box. It’s a “feature” (bug), see: While editing, you can also decide which shortcuts you want to put on the Desktop. In Fig 2, I’ve chosen to put the Excel shortcut on both the Desktop ([DesktopFolder]) and in the Start menu ([ProgramMenuFolder]\Microsoft Office 2016).
    • Update: More bugs, sigh. Skype for Business shortcut launches “Skype for Business Recording Manager” instead of “Skype for Business”. I’m getting very frustrated with all the bugs. See: Anyways, I corrected the mistake MS has made. In Fig 3 target should definitely not be Skype for Business Recording Manger, instead it should be “Skype for Business 2016”. At least so I thought. After this “fix” (and a test-deployment), my reward was the following message:


                   Fig 1. Problem with shortcut

      • Only solution (as I can see it), is to force-copy a “functional shortcut icon” during deployment. With functional I mean a shortcut that has been manually created from C:\Program Files (x86)\Microsoft Office\Office16\lync.exe. (You should also remove all the Skype for Business shortcut entries in OCT).
        • This shortcut will then be deployed as a dependency. See later chapter about dependencies.


                    Fig 2. Office 2016 OCT.


                   Fig 3. Skype for Business – example shortcut entry.



Deploying Office 2016/Removing Office 2013/2010

The deployment part was quite straight forward after the customization part was done. Well, so I thought. Instead I got to fiddle with SCCM’s supersedence and dependencies. More on that later on. As with Office 2013, I basically followed a (very good) guide from with some changes for our environment. In this case the changes were about uninstalling Office 2013 SP1, as a fully patched Office 2013 SP1 WON’T get uninstalled even though I’ve chosen to remove previous versions in the OCT (see my (unanswered) post at The remove previous version-feature works with Office 2010 however, which means I don’t have to create a supersedence relation for that version (luckily).


Checking parameters for Office 2013 uninstallation:

In order for Office 2013 SP1 to be silently removed via SCCM before you install Office 2016, you have to be sure that the uninstall string is correct and a custom xml-file is in place. I’m already using “custom.xml” (together with OCT) for the installation part, so I’ve created a custom2.xml for the uninstallation part. (The reason for this is also a bug, see my Office 2013 deployment post). This file contains only the following information:

<Configuration Product=”ProPlus”>
<Display Level=”none” CompletionNotice=”no” SuppressModal=”yes” AcceptEula=”yes” />

You’ll then use this xml file when you specify the uninstall string for Office 2013 SP1 in SCCM. See for details. If you don’t specify a xml file, the uninstallation won’t be silent. This in turn means it won’t be successful.



With all the parameters done, you can now deploy Office 2016 using Office 2013 as Supersedence (see screenshot below):


Fig 4. Supersedence.


All of the fields aren’t visible in the screenshot, but the fields are:

This application supersedes the following applications:

Application: Microsoft Office 2013 SP1
Old Deployment Type: Microsoft Office Professional Plus 2013 SP1 installer
Replacement Deployment Type: Microsoft Office Professional Plus 2016 installer
Active: Yes
Uninstall: Yes (Tick in the box)

That’s it! All the other options are the same as with a regular deployment, you just add the Supersedence-information (so Office 2013 SP1 can be successfully removed while installing Office 2016). This would not be successful without having specified the correct uninstallation information/parameters for Office 2013 SP1.

A general rule of thumb when using supersedence is that you must have the correct uninstall information specified for the application that will be superseded!


Update! I had problems deploying Office as “available”. (“Required” was working ok though). I always got the message “Past due – will be updated”  in Software Center (see Fig 5). This on the other hand made Office upgrade itself without user interaction, which was not acceptable for a deployment that was made available (NOT required).


Fig 5. Past due – will be updated.

After some googling I found out it was related to the supersedence settings. Don’t know if you can call this a bug, but at least it was working better if I followed this advice:

“Regarding the issue where you have set the application to AVAILABLE in the deployment, however the application uninstalls and upgrades without user interaction, I have found a work around.

The cause of the problem seems to be when you are deploying an application to a collection the option “automatically upgrade any superseded versions of this application” is CHECKED and GREYED out. If you deploy the application BEFORE you have added the supersedence, this will NOT be greyed out and you will be able to leave it unchecked. Then add the supersedence rules.

Hope this helps



I mentioned the word better because even now it wasn’t working 100% correct. I had to click “Install” in Software Center about 3-4 times (for all the dependent applications). This wasn’t acceptable, as the software (and all its dependencies) should install with just ONE click. Well, I found the solution to this dilemma myself – just leave one checkbox unticked. (See Fig 6).


Fig 6. Supersedence checkbox.

With all this done, the deployment was running smoothly with just one click (“Install”) in Software Center.



Customizing Office Proofing Tools Kit 2016

This is actually the exact same procedure as for Proofing Tools 2013. I’ll copy/paste the information from my previous blog post:

We are using a Proofing Tools “CD” which include all of the proofing tool languages. Proofing Tools doesn’t/can’t change the UI language btw, only check the spelling. With this CD, there’s no need to separately download each proofing tool language. After some googling I stumbled upon which seemed useful. Once again I’ll share my configuration so you don’t have to scratch your own head. First off, there’s NO “setup.exe /admin” on this cd/iso. You have to manually specify the languages (proofing) you need. The file you need to edit is located on the extracted iso, in my case \Office_2016_Proofing_Tools\proofkit.ww\config.xml. My file looks like this:

<Configuration Product=”Proofkit”>

   <Display Level=”none” CompletionNotice=”no” SuppressModal=”yes” AcceptEula=”yes” />
   <COMPANYNAME Value=”Abo Akademi” />
   <OptionState Id=”ProofingTools_1053″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1030″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1035″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1036″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1031″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1032″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1040″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1044″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_2068″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1045″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1049″ State=”local” Children=”force” />
   <OptionState Id=”ProofingTools_1048″ State=”local” Children=”force” />
   <Setting Id=”SETUP_REBOOT” Value=”Never” />

This bypasses all prompts in the setup process and installs 12 proofing languages. I’m not going to list the languages here (OptionState Id values), as there’s a table is available at: . That’s it for the configuration – now ready for deployment.

Source: plus all the forgotten ones…



Deploying Office Proofing Tools Kit 2016/Removing Proofing Tools Kit 2013/2010

Again, this information is already available in my previous blog post about deploying Office Proofing Tools 2013 SP1, so please have a look there before you continue. The difference in the 2016 version is that I will use different dependencies so that Office and the language packs will be installed before proofing tools is installed. I’ll also use Supersedence the same way I did with Office 2016 above.


Checking parameters for Proofing Tools Kit 2013 SP1 uninstallation:

First off, I’ll copy/paste information about the installation:

Edit “programs” tab. The installation command should now include our custom.xml instead of the MSI-command. The command I used was: setup.exe /config \\sccm2012r2\Software\Office_2013_Proofing_Tools_with_Service_Pack_1\proofkit.ww\config.xml.  ( /config .\proofkit.ww\config.xml is an easier/better syntax, btw)

Ok – now you just have to add the uninstall information with a custom UNintallation xml-file. This is also the same procedure as with the Office 2016 deployment. Anyway, I created a custom2.xml file in the same location as mentioned above. It includes basically the same information as the Office 2013 SP1 uninstallation xml-file:

<Configuration Product=”Proofkit“>
   <Display Level=”none” CompletionNotice=”no” SuppressModal=”yes” AcceptEula=”yes” />

…and for the uninstall program string I’m using: setup.exe /uninstall Proofkit /config .\proofkit.ww\config2.xml

There you have it. Both the install and the uninstall information are now specified. You can now go ahead and make the same Supersedence rules as with the Office 2016 deployment:


Fig 7. Office 2016 Proofing Tools Kit – Supersedence.

Only thing that isn’t fully visible in the screenshot above is Application: Microsoft Office Proofing Tools 2013 SP1 – multilanguage.


You can now do the same thing with Office 2010 (proofing tools) if you want to uninstall/remove that version prior to installing proofing tools 2016. You’ll then add another supersedence rule so that BOTH Office 2010 proofing tools AND Office 2013 proofing tools will be superseded (both should then be in the list in Fig 7).

(I also specified the uninstallation information for Office 2016 (proofing tools) already, so I can (hopefully) use the same procedure with future versions of Microsoft Office).



Deploying Office 2016 language packs/Removing Office 2013/2010 language packs

Some people are also using language packs with Office so I had to figure out a way of uninstalling/replacing these with the 2016 versions. Luckily, Microsoft is using quite the same syntax as with proofing tools, so the task was actually quite simple. You start with the same procedure – checking for a valid config.xml file for installation and uninstallation. The difference here is that you don’t need any specific codes for the language(s), as one language pack only installs one specific language. Another nice thing is that you can use the SAME xml file for both the installation and the uninstallation. The custom.xml for the Swedish language looks like this for example:

<Configuration Product=”“>
<Display Level=”none” CompletionNotice=”no” SuppressModal=”yes” AcceptEula=”yes” />

That’s the only thing you need to add/edit to make it silent. The paths for the files are a bit different than with Proofing tools though. The xml file is in .\ (or omui.xx.xx for your own language). The MSI-file used for deployment (or actually only for getting the product code) is in the same directory. The installation and uninstallation strings are also a bit different. Let me show you some screenshots and whatnot from SCCM;

First off you create a new application and make it think it’s going to use a MSI file for installation. You do it like this only to get the detection method/MSI product code.


Fig 8. Create Application Wizard.

Use the path:

\\yoursccmserver\yoursoftwarestash\Microsoft Office 2016 Multilang Pack Swedish\ (or similar for another language) and select the file OMUI.msi

Then just next, next, next through the wizard.


After this, you edit the Deployment Type. First off, go to the Content location. This path will be wrong (as the msi file is located in the -directory). Please REMOVE “” from the content location path. That’s it for content.


Fig 9. Office 2016 Swedish language pack content location.


Then you move over to the “Programs” tab. Here’s where the magic happens. Remove all the text/strings from installation program and uninstallation program. Replace the text with the one from Fig 10 below.


Fig 10. Programs tab. Same xml is used for both installation and uninstallation (even though it got cut off in the screenshot).

In case you want to copy/paste, the strings are:

Installation program: setup.exe /config .\\config.xml
Uninstall program: setup.exe /uninstall /config .\\config.xml




You can now do the same thing with Office 2010 language packs if you want to uninstall/remove that version prior to installing the 2016 language packs. You’ll then add another supersedence rule so that BOTH Office 2010 language pack AND Office 2013 language pack will be superseded.




There you have it. You’ve created separate SCCM applications for Office 2016, Office 2016 Proofing tools and Office 2016 language packs. That’s very nice and all, but you probably don’t like deploying all three applications separately, now do you? Well, this is where dependencies comes into play.

In my final deployment I ONLY deploy Office 2016 Proofing Tools. Office 2016 (main office application) is a dependency of Proofing tools, and so are the language packs. See Fig 11 for details.

NOTE! Create THREE SEPARATE dependency groups, otherwise there will be an “OR” statement instead of “AND”. I learned this the hard way when the language packs wouldn’t install. See the right and the wrong way to create the dependencies below:   


Fig 11. Office 2016 Proofing Tools dependencies, the CORRECT way.



Fig 12. Office 2016 Proofing Tools dependencies, the WRONG way.


Update! As there is a bug with the Skype for Business shortcut icon, the shortcut will also be deployed as a dependency.

  • First, create a new directory for your application (script) on your sccm server. Call it “Office2016-Skype for Business-shortcut” for example.
  • Copy the Skype for Business 2016-custom shortcut (created in the Customizing Office 2016 part) to this directory.
  • Create two new PowerShell script files in this directory;
    • Name the scripts Office2016-Skype for business-shortcut.ps1 and Office2016-Skype for business-shortcut-uninstall.ps1 for example.
      • Contents of Office2016-Skype for business-shortcut.ps1:

copy “Skype for Business 2016.lnk” “C:\Users\Public\Desktop\Skype for Business 2016.lnk”
copy “Skype for Business 2016.lnk” “C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\Skype for Business 2016.lnk”

      • Contents of Office2016-Skype for business-shortcut-uninstall.ps1:

del “C:\Users\Public\Desktop\Skype for Business 2016.lnk”
del “C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\Skype for Business 2016.lnk”


Create a new application using the following information:

  • Create Application
  • Manually specify the application information
  • Name: whatever you like
  • Deployment Types:
    • Type: Script Installer
    • Manually specify the deployment type information
    • Name: whatever you like
    • Content location: wherever you store your applications, in a subdirectory called “Office2016-Skype for Business-shortcut” for example
    • Installation program: powershell.exe -executionpolicy Bypass -file “Office2016-Skype for business-shortcut.ps1”
    • Uninstallation program: powershell.exe -executionpolicy Bypass –file “Office2016-Skype for business-shortcut-uninstall.ps1”
    • Detection method: Setting type: File System: C:\Users\Public\Desktop\Skype for Business 2016.lnk AND C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Office 2016\Skype for Business 2016.lnk exist.
    • Install for system, whether or not a user is logged on, hidden.

With all this done, create yet another dependency group. Add this newly created application to this dependency group. You then have a total of FOUR dependencies.





Let’s sum it up:

  • Actual deployment “object” is Office 2016 proofing tools, which in turn use dependencies (main office application + language packs).
  • Office 2016 supersedes Office 2013.
  • Office 2016/2013 removes/uninstalls Office 2010 via OCT (no supersedence).
  • Proofing tools packages use supersedence to update themselves to the newest versions (2010 –> 2016,  2013->2016).
  • Language pack packages use supersedence to update themselves to the newest versions (2010 –> 2016,  2013->2016).
  • Office 2016 is a dependency of Proofing tools 2016, and so are the language packs (and the Skype for Business shortcut).
  • A shortcut for Skype for Business is created in the Start menu and on the desktop. (It is NOT created via OCT as it creates a faulty one).


And some screenshots:


Fig 13. Downloading all of the needed packages/applications.

The number of downloaded components depend on which Office-components SCCM detects on the client. (Is Office already installed and are only the language packs needed, for example). I’ve now changed the application catalogue name of the “main” application to something more convenient than “Microsoft Office Proofing Tools 2016 – multilanguage”. Currently I’m using “Office 2016 with proofing tools + swe & fi language packs”.



Fig 14. Updating components. One done, seven to go.



Fig 15. Before Office 2016 deployment. Office 2013 SP1 (with Proofing tools and two language packs). Same components are installed if using Office 2010.



Fig 16. After successful Office 2016 deployment. Same thing, new versions🙂


Success!🙂 (Finally… after many, many, many hours of testing)


BTW, feel free to comment if you find this post useful.