Derek Souter - the grumpy IT Admin
Derek Souter - the grumpy IT Admin


Just a little blog for IT pro's

Wednesday, 15th April 2020


Today is my last day with DXC.


After 3 years and 8 months, I am leaving - for various reasons.

I start a new role on Monday the 20th of April.   Doing internal support for Devro.


I am looking forward to the new challenges, but must admit to some trepidation at leaving a comfortable role during the current Coronavirus pandemic.

It will be odd starting a new job and not being able to go to the office to meet my colleagues.


Monday, 2nd March 2020


Note to self:


when configuring your development environment, and you want to load balance using Round Robin DNS on a windows server - make sure that you change the Time To Live (TTL) on each record for the load balance name to zero, rather than the default of 1 hour.   Otherwise you may find yourself wondering why you are always directed to the same server.....






Anyway, on with some PowerShell scripting.


Friday, 31st January 2020


Well, it's been an interesting few months - with all sorts of issues both in work and in my personal life.


However, one of our customers had an issue with SharePoint MySites.   what happened was at some point in the past, the User Profile Service was rebuilt, and a new connection put in place - with the option set to filter disabled users.

The customer very rarely deletes any users from AD - for "reasons".


Scroll forward a few months, and they start complaining about MySites still existing for users that have long since left - and they are persuaded to have the AD accounts deleted.


We then notice that the MySites cleanup is not actually doing anything.


After giving it a few slaps, it springs in to life, and proceeds to merrily start removing all the user profiles for the deleted AND the disabled users - sending the usual emails to the managers along the way.


This then caused the customer to complain both about the emails, and the fact that the disabled users data is not to be removed.........


So, quickly change the setting in the Profile Sync to no longer filter disabled users, and another import - and suddenly the users are back in the User Profile database.


One small problem - the users are no longer connected to their MySites.


I could have gone in and manually reconnected all the users to their MySites - there were only a few hundred......


Or, I could try and find a script to do it automatically for me.


Couldn't find anything appropriate - so I took some components from others and wrote my own.


Very simply, it reads all the user profiles, checks for any that show the MySite has been created, but the field is not populated - then checks to see if a MySite exists for that username - and then clears and populates the field.

It will also check for any users that have the Personal site populated, but the site does not exist, and will then clear the field.


It seemed to work as expected - and I have seen no issues with the users.

Obviously, I ran it a couple of times with the commit() entries being commented out, and then reviewed the logs to make sure that it would work as I expected.


MySite Relink Script
A quick and dirty PowerShell script to automate linking SharePoint User Profiles back to their MySites after they are reimported to the User Profile Database
MySite Relink.txt
Text document [2.5 KB]

add-pssnapin Microsoft.SharePoint.PowerShell -ErrorAction Silentlycontinue

$tracefile = ".\personsitetranscript_" + (get-date).tostring("dd-MM-yyyy-hh-mm-ss") + ".txt"
start-transcript -path $tracefile
$UPM = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager([Microsoft.Office.Server.ServeContext]::Default)
$MySiteURL = $($UPM.mysitehosturl) -replace ".{5}$"
$servicecontext = get-spservicecontext -site $MySiteURL

$usercount = 0
$sitestoprocess = 0
$ProvisionedUsers = 0
$DeProvisionedUsers = 0
$allprofiles = $upm.getenumerator() | where { $_.displayname -ne ""}

foreach($profile in $allprofiles)
$personalsite = $profile.Personalsite
$personalsitecreated = $profile.PersonalSiteInstantiationState
$username = $($profile.accountname).split("\")[1]
if(($personalsitecreated -ne $null) -and ($($personalsite.url) -eq $null))
write-output "Profile number [$usercount] - for user [$username] -shows site created, but personal site is empty [$($personalsite.url)]"
$userURL = "$MySiteURL/personal/username"
write-output "Checking if MySite [$userURL] exists"
$MSite = get-spsite $userURL -ErrorAction silentlycontinue
if($MSite -ne $null)
write-output "MySite exists"
write-output "Attempting to set Personal site URL to [$userURL]"
$profile[[Microsoft.Office.Server.userProfiles.PropertyConstants]::PersonalSpace].Value = ""
$profile[[Microsoft.Office.Server.userProfiles.PropertyConstants]::PersonalSpace].Value = "/personal/$username"
write-output "Profile for [$username] has been updated"
write-output "Profile number [$usercount] - for user [$username] - shows site created, verifying that MySite exists"
$userURL = "$MySiteURL/personal/$username"
write-output "Checking if MySite [$userURL] exists"
$MSite = get-spsite $userURL -ErrorAction silentlycontinue
if($MSite -eq $null)
write-output "MySite does not exist, but is set in Profile - removing"
$profile[[Microsoft.Office.Server.userProfiles.PropertyConstants]::PersonalSpace].Value = ""
write-output "Profile for [$username] has been updated"
write-output "Script should set [$sitestoprocess] users"
write-output "Script has added correct entry to [$provisionedUsers] users"
write-output "Script has removed invalid entry from [$DeProvisionedUsers] users"

Tuesday, 9th July 2019


So, I have been saying for a while now that the passwords used for our managed accounts in SharePoint can be retrieved (and told that I was wrong).

I did a quick bit of internet searching, and came up with a one line command that will retrieve the passwords - this worked in my SharePoint 2013 DEV environment.

this is because the application pool identity is the same, and the passwords for that can be retrieved very easily (you just need the name of the application pool that is running under the identity you are interested in) - open an administrative powershell prompt (you need to be at least a local admin on the server, and possibly a farm admin as well), and run the single line command shown below

Cmd.exe /c c:\windows\system32\inetsrv\appcmd.exe list apppool “name of an application pool” /text:processmodel.password


I then put that into a simple PowerShell script, to loop through all application pools and output the details.


Recover Application Pool usernames and passwords

$apppools = cmd.exe /c c:\windows\system32\inetsrv\appcmd.exe list apppool
foreach ($apppool in $apppools)
    $poolname = $apppool.split('{"}')
    $checkname = '"' + $poolname[1] + '"'
    $currentuser = cmd.exe /c c:\windows\system32\inetsrv\appcmd.exe list apppool $checkname /text:ProcessModel.username
    $password = cmd.exe /c c:\windows\system32\inetsrv\appcmd.exe list apppool $checkname /text:ProcessModel.Password
    write-host "Application Pool [$checkname] is running under identity [$currentuser] with password [$password]"

Wednesday, 3rd July 2019


I sometimes find myself writing random bits of code, just to pass the time - I wanted to encrypt and decrypt strings of text, so came up with a bit of code that I will be able to plug in to other scripts

Written in PowerShell



Simple test of an encryption and decryption module


Function Create_EncryptionKey
    write-host "Choose to provide a previous Encryption key, or generate new key"

Function New_encryption_Key
    # Create an array of random numbers to be used as the encryption key
    $global:keyarray = @()
    $keylength = 32
    $arrayentry = 0
        $randomentry = 0
        $randomentry = get-random -minimum 1 -maximum 256
        $global:keyarray += $randomentry
    until ($arrayentry -eq $keylength)
    write-host "Encryption key is: [$global:keyarray]"

Function ConvertTo-UnsecureString 
    Param (
    Try {
        $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($ss)
    Finally {

Function Encrypt_Secure_String
    # encrypt the secure string
    $global:fullyencrypted = ConvertFrom-SecureString -SecureString $encryptedstring -key $global:keyarray

    # show the fully encrypted string
    write-host "Encrypted string is : [$global:fullyencrypted]"

Function Decrypt_Secure_String
    # decrypt the secure string - this is NOT the encrypted string!
    $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encryptedstring))

    write-host "Decryption of the secure string is : [$UnsecurePassword]"


# read the string that is to be encrypted
$encryptedstring = Read-host -AsSecureString "Enter String to encrypt"



# decrypt the encrypted string back to plain text - will only work for the current session
$newdecrypt = ConvertTo-SecureString -String $global:fullyencrypted -Key $global:keyarray
$decrypted = ConvertTo-UnsecureString $newdecrypt
write-host "Decryption of the fully encrypted string, using the provided Encryption key is : [$decrypted]"

Thursday, 27th June 2019


Team 1 - we need to access this folder on all these servers, what's the best way to do that
Me - create a file share on each of the servers
Team 1 - sounds good - who would do that
Me - best to do it using a Group Policy, that way it is consistent and will catch all future servers, and is easy to update later
Team 1 - OK
Active Directory Team - Oh, that's complicated, we don't have time to do that at the moment - best if you just go and create them all manually on all the servers....
Me - ??? Really ????

Me (email to all concerned) - I just did it with a simple Group Policy object in my Development environment - it took around 30 seconds - do you really think I should still do this manually on all the servers?

Currently waiting for a response.......................................


Monday, 9th July 2018


So, I needed a script to backup the farm configuration for SharePoint.   After fighting with some people that disagreed with the permissions required, we finally got agreement to setup the necessary permissions and the scheduled task.


I used a few bits of code that I found online, and created a script that works in every environment.


It is designed to run on a server that is running Central Administration, and backs up the configuration to a file share that it expects to find on another server also running Central Administration, or to a file share hosted locally.


it will also create both a debug log and a backup log (in a sub-folder) - and cleans up any backups or logs that are older than the specified number of days.


I have also created a very basic work instruction on how to setup the scheduled task.


SharePoint Configuration Backup
PowerShell script that can be run manually or as a scheduled task to backup the Farm Configuration
Text document [12.2 KB]
Instructions to run Scheduled task
follow these very basic instructions to run the backup script as a scheduled task
Backup Work Instructions.docx
Microsoft Word document [18.0 KB]

Thursday, 21st December 2017


Merry Christmas


This week I found myself in need of a populated domain - with several hundred thousand user accounts.


A little bit of digging online led me to a script by Helge Klein (, and a modified version by Rob Bridgeman on GitHub (   


Neither worked exactly for me, so I had to adjust them.


I now have a script that works perfectly in my environment, and I used to create 250000 users spread over 11 OU's.   My next project will be to extend the list of addresses, create some random groups, add the users into the groups at random during the creation process, and maybe create some random OUs as well.


This is my version of a PowerShell script to populate Active Directory with some test user accounts
Demo Users creation.txt
Text document [8.1 KB]
This is one of the backing files required for the PowerShell script
Save it in the same folder as the script
This is the list of Addresses, feel free to extend it - please note, that any postcodes listed in this file must have a matching code in the PostalAreaCode file
Text document [335 Bytes]
This is one of the backing files required for the PowerShell script
Save it in the same folder as the script
This is the list of first names, taken from 1990 US Census data
Text document [40.3 KB]
This is one of the backing files required for the PowerShell script
Save it in the same folder as the script
This is the list of surnames, taken from 1990 US Census data
Text document [634.8 KB]
This is one of the backing files required for the PowerShell script
Save it in the same folder as the script
This is the list of Postcodes, and maps to the telephone number area code, feel free to extend it
Text document [1.4 MB]

#Credit for original script to Helge Klein
#Adapted to allow higher numbers of users with the same information set.

# Summary of changes.
# Reduced Male and Female names into one list for ease of expansion
# Changed Displayname code to create each combination of names possible
# Changed sAMAccountname generation to add unique account ID with orgShortName as suffix.

# Derek Souter - 20/12/2017
# Updated to ensure that both firstnames and lastnames are created randomly
# Added list of OUs, and ensured that users are created randomly within those OUs
# this version was tested to create 250000 users in 11 different OUs

# Known issues
# Usercount (For me anyway) seems to be inaccurate when import completes. May be related to errorcheck compensation when usercount is reduced. Consistently seem to get many more users that intended.

Set-StrictMode -Version 2

Import-Module ActiveDirectory

write-host "import AD"

# Set the working directory to the script's directory
Push-Location (Split-Path ($MyInvocation.MyCommand.Path))

# Global variables
# User properties
$ou = "OU=User Accounts,OU=Accounts,DC=domain,DC=uk"         # Which OU to use as the root to create the users in.   
$initialPassword = "Password1"               # Initial password set for the user
$orgShortName = "TEST"                         # This is used to build a user's sAMAccountName
$dnsDomain = "TEST.DOMAIN"                      # Domain is used for e-mail address and UPN
$company = "Sandbox environment"                           # Used for the user object's company attribute
$departments = (                             # Departments and associated job titles to assign to the users
                  @{"Name" = "Finance & Accounting"; Positions = ("Manager", "Accountant", "Data Entry")},
                  @{"Name" = "Human Resources"; Positions = ("Manager", "Administrator", "Officer", "Coordinator")},
                  @{"Name" = "Sales"; Positions = ("Manager", "Representative", "Consultant")},
                  @{"Name" = "Marketing"; Positions = ("Manager", "Coordinator", "Assistant", "Specialist")},
                  @{"Name" = "Engineering"; Positions = ("Manager", "Engineer", "Scientist")},
                  @{"Name" = "Consulting"; Positions = ("Manager", "Consultant")},
                  @{"Name" = "IT"; Positions = ("Manager", "Engineer", "Technician")},
                  @{"Name" = "Planning"; Positions = ("Manager", "Engineer")},
                  @{"Name" = "Contracts"; Positions = ("Manager", "Coordinator", "Clerk")},
                  @{"Name" = "Purchasing"; Positions = ("Manager", "Coordinator", "Clerk", "Purchaser")}
$OUs = ("AAA","BBB","CCC","DDD","EEE","FFF","GGG","HHH","III","JJJ","KKK")   # OUs to add to the $ou variable to create users in.  this is concatenated with the ou variable later in the script
$phoneCountryCodes = @{"GB" = "+44";"JP" = "+81";"US" = "+01"}         # Country codes for the countries used in the address file

# Other parameters
$userCount = 250000                           # How many users to create
$locationCount = 4                          # How many different offices locations to use

write-host "Creating "$usercount " users in "$locationcount " locations"

# Files used
$firstNameFile = "Firstnames.txt"            # Format: FirstName
$lastNameFile = "Lastnames.txt"              # Format: LastName
$addressFile = "Addresses.txt"               # Format: City,Street,State,PostalCode,Country
$postalAreaFile = "PostalAreaCode.txt"       # Format: PostalCode,PhoneAreaCode

# Read input files
write-host "Importing first names"
$firstNames = Import-CSV $firstNameFile
$firstnamecount = $firstNames.count-1
write-host "Imported "$firstnamecount" first names"

write-host "Importing last names"
$lastNames = Import-CSV $lastNameFile
$lastnamecount = $lastNames.count-1
write-host "Imported "$lastnamecount" last names"

write-host "Importing addresses"
$addresses = Import-CSV $addressFile

write-host "Importing postcodes"
$postalAreaCodesTemp = Import-CSV $postalAreaFile

# Convert the postal & phone area code object list into a hash
$postalAreaCodes = @{}
foreach ($row in $postalAreaCodesTemp)
   $postalAreaCodes[$row.PostalCode] = $row.PhoneAreaCode
$postalAreaCodesTemp = $null

# Preparation
$securePassword = ConvertTo-SecureString -AsPlainText $initialPassword -Force

# Select the configured number of locations from the address list
$locations = @()
$addressIndexesUsed = @()
for ($i = 0; $i -le $locationCount; $i++)
   write-host "setting a random address"
   # Determine a random address
   $addressIndex = -1
      $addressIndex = Get-Random -Minimum 0 -Maximum $addresses.Count
   } while ($addressIndexesUsed -contains $addressIndex)
   # Store the address in a location variable
   $street = $addresses[$addressIndex].Street
   $city = $addresses[$addressIndex].City
   $state = $addresses[$addressIndex].State
   $postalCode = $addresses[$addressIndex].PostalCode
   $country = $addresses[$addressIndex].Country
   $locations += @{"Street" = $street; "City" = $city; "State" = $state; "PostalCode" = $postalCode; "Country" = $country}
   # Do not use this address again
   $addressIndexesUsed += $addressIndex

# Create the users

# Randomly determine this user's properties
# Sex & name
$i = 0
write-host "set user"
while ($i -lt $userCount) 
    $FirstnameIndex = Get-Random -Minimum 0 -Maximum $firstNames.count
    $LastNameIndex = Get-Random -Minimum 0 -Maximum $lastnames.count
    $Fname = $firstnames[$FirstnameIndex].Firstname
    $Lname = $lastNames[$LastNameIndex].Lastname

    $displayName = $Fname + " " + $Lname

   # Address
   $locationIndex = Get-Random -Minimum 0 -Maximum $locations.Count
   $street = $locations[$locationIndex].Street
   $city = $locations[$locationIndex].City
   $state = $locations[$locationIndex].State
   $postalCode = $locations[$locationIndex].PostalCode
   $country = $locations[$locationIndex].Country
   # Department & title
   $departmentIndex = Get-Random -Minimum 0 -Maximum $departments.Count
   $department = $departments[$departmentIndex].Name
   $title = $departments[$departmentIndex].Positions[$(Get-Random -Minimum 0 -Maximum $departments[$departmentIndex].Positions.Count)]

   # OU to place
   $UserOUIndex = Get-Random -Minimum 0 -Maximum $ous.count
   $UserOU = $OUs[$UserOUIndex]
   $fullUserOU = -join("OU=",$UserOU,",",$OU)

   # Phone number
   if (-not $phoneCountryCodes.ContainsKey($country))
      "ERROR: No country code found for $country"
   if (-not $postalAreaCodes.ContainsKey($postalCode))
      "ERROR: No country code found for $country"
   $officePhone = $phoneCountryCodes[$country] + "-" + $postalAreaCodes[$postalCode].Substring(1) + "-" + (Get-Random -Minimum 100000 -Maximum 1000000)
   # Build the sAMAccountName: $orgShortName + employee number
   $employeeNumber = Get-Random -Minimum 100000 -Maximum 1000000
   $sAMAccountName = $orgShortName + $employeeNumber
   $userExists = $false
   Try   { $userExists = Get-ADUser -LDAPFilter "(sAMAccountName=$sAMAccountName)" }
   Catch { }
   if ($userExists)
      if ($i -lt 0)

   # Create the user account
      New-ADUser -SamAccountName $sAMAccountName -Name $displayName -Path $fulluserou -AccountPassword $securePassword -Enabled $true -GivenName $Fname -Surname $Lname -DisplayName $displayName -EmailAddress "$Fname.$Lname@$dnsDomain" -StreetAddress $street -City $city -PostalCode $postalCode -State $state -Country $country -UserPrincipalName "$sAMAccountName@$dnsDomain" -Company $company -Department $department -EmployeeNumber $employeeNumber -Title $title -OfficePhone $officePhone

   "Created user #" + ($i+1) + ", $displayName, $sAMAccountName, $title, $department, $street, $city"
   $i = $i+1
   #$employeeNumber = $employeeNumber+1

      if ($i -ge $userCount) 
       "Script Complete. Exiting"

Friday, 24th November 2017



SharePoint 2013 - change license key


A large part of my job is working on SharePoint 2013 environments, many of which were installed with the incorrect (Standard) License key.


After some major issues changing from a Standard to an Enterprise license key, I came up with a very simple method that works for me - YMMV


1.  Logon to the SharePoint Central Admin server

2.  Select "Upgrade and Migration"

3.  Select "Enable Enterprise Features"

4.  Select the radio button for Enterprise

5.  Enter the Enterprise key

6.  Click OK

7.  wait until the error message is displayed

8.  Open a SharePoint PowerShell as an administrator

9.  Enter the command "set-spfarmconfig -InstalledProductRefresh"

10.  Go back to the SharePoint Central Admin server

11.  Select "Upgrade and Migration"

12.  Select "Enable Features on Existing Sites"

13.  Wait for it to complete


you should now have changed to an enterprise license key

Wednesday, 24th May 2017


Wannacry Virus


I really wish the media would stop calling the Wannacry virus a "hack" or saying it was an attack on the NHS.  

It was a nasty virus that was sent to hundreds of millions of email addresses across the globe. 

People opened the attachment (these are generally either a macro enabled word document or a PDF), allowed the embedded script to run, which then went off to the internet to download the really bad payload which then infects the local machine, which then went off to the local network to infect any vulnerable unpatched systems it could find.  

The NHS  IT staff  were in compliance of UK government regulations regarding patching (all critical and security patches  must be installed within 3 months of release by vendor). 

The patch had been released the month before by Microsoft, and was probably still being rolled out to the test systems, before being deployed to all live systems.


If you want to stop this sort of thing from affecting your network, you can do various things - but none of them individually will work


1.            Have decent perimeter email scanning in place, to limit the number of spam and infected emails that get in to your organisation.

2.            Have security policies in place to stop word documents running macros (same for PDF files).

3.            Have a proxy server that can scan for malicious payloads in the downloads – and maybe even redirect any unusual web requests to a confirmation page before allowing access.

4.            Use the security tools provided to limit the access users have to only what is required to do their job – do they need to be able to delete files from a specific network share?

5.            Use Windows File Filters on the server to prevent the creation of the encrypted file types (it is hard to keep up, but there is currently no easy way to limit the creation of files to an allowed list of file types - file screens are good, but not perfect) – currently the ransomware will not delete the files if it cannot encrypt them (that is likely to change soon).

6.            Ensure you have a decent backup strategy for your data – make sure you know where it is, and that it can be recovered quickly and easily.  Assume that you WILL get hit with something at some point, plan for it.

7.            Train the staff in identifying spam and possibly infected emails, and what not to do with the attachments – they are your first line of defence, and “oh, I don’t really know about computers” is not a decent response in this day and age – sorry people, but if you use a computer every day, you should know at least some basics in how to protect yourself.

8.            Listen to your IT department – they are not simply a cost to your business, often they are the ones keeping everything running so that everyone else can do their job.    And if you constantly ignore their suggestions, then they will stop making them, and the good ones will leave.

Wednesday, 11th January 2017


Happy New Year.


Always fun when someone from a place you used to work contacts you to find out who deals with something, and to let you know that there is a major issue there.   Exactly the sort of thing that would have had me laughing all day, but it's widely known that i have a sick sense of humour.


1&1 currently also have a "glitch" with the domain renewals - I transferred two domains to them, which both were renewed for 2 years.  At the same time I noticed that another domain was due to renew this year, so I renewed it for 2 years at the same time - got an email that said "I am pleased to inform you that your domain ************* has successfully renewed for the next 2 year(s). The next renewal date is set for 20.05.2018."   when I contacted them, initially the person didn't understand the issue with that email.................they then said that they would get someone to contact me.  this morning I contacted them again - and was informed that it was a "glitch" in the renewal process, and that they were working on fixing it.   I also pointed out that it would have been nice if someone had even dropped me an email to let me know.


Oh well, guess I just need to wait and see how long it takes them to sort it, and if they bother to contact me once it is resolved.

Monday, 19th December 2016


Merry Christmas,  what a fun few weeks it's been.   So, we have an issue attempting to add a new node to an existing 2 node SQL cluster.   The team tried (and failed) to add the freshly built node to the cluster.   It failed validation because the MPIO driver on the new node is newer than the existing nodes.  Solution suggested by the team - downgrade the MPIO driver on the new node to the same as the existing nodes.   Guess what, it didn't work.   Solution suggested by myself - look at upgrading the MPIO driver on the existing nodes.    Now, we need to spend even more time discussing this, and trying to work out another solution.    Looks like we will be well in to January before get this sorted.     Well, at least it's a problem for another team.  Happy holidays.

Tuesday, 4th October 2016.


Well, it's been an interesting few months.  I changed jobs a little over a month ago.  I am now working for HPE, spending most of my time in meetings (after spending over a month doing nothing).

Friday, 20th May 2016
TGIF! (Thank God It's Friday)

Another week finished, time to think about photo's I can take this weekend


Print Print | Sitemap
© Derek Souter