AutoSPInstaller: Extending and Customizing

SharePoint
This entry is part 4 of 4 in the series AutoSPInstaller

AutoSPInstaller gets you up and running fast. But, there may be times when the PowerShell functions that are included don’t suit you or your environment. You may also want to automate a few other related tasks.

In this post I’m going to show you a few examples of how to extend AutoSPInstaller to perform additional tasks. I’ll also show you one way to override functions and use your own instead. So, lets get started.

Extending

The context in which I’m using the term “Extended,” simply means adding your own functions to AutoSPInstaller. The best way to do this is to create your functions and place them in the AutoSPInstallerFunctionsCustom.ps1 that comes with the package. As an example, the file already contains one simple function called “GetVersion.”

Let’s take a real life example. In an earlier post AutoSPInstaller: Getting Prepared, I shared a tip on Step 3. The tip is to add some common executable extensions to the “Inclusion list for low file types,” which prevents popups during install. I want to automate that so I don’t forget. So, I’ve created a PowerShell function to do this for us:

#Region Add Low Risk FileTypes
# ===================================================================================
# FUNC: Add-LowRiskFileTypes
# DESC: Adds the specified extensions to the low risk file types (prevents popups)
# ===================================================================================
Function Add-LowRiskFileTypes(){
 $Lowriskregpath ="HKCU:\Software\Microsoft\Windows\Currentversion\Policies\Associations"
 $Lowriskregfile = "LowRiskFileTypes"
 $LowRiskFileTypes = ".exe;.msp;.msi;"
 New-Item -Path $Lowriskregpath -erroraction silentlycontinue | Out-null
 New-ItemProperty $Lowriskregpath -name $Lowriskregfile -value $LowRiskFileTypes -propertyType String -erroraction silentlycontinue | Out-null
 Write-Host "Added Low Risk File Types $LowRiskFileTypes"
 }
Function Remove-lowriskfiles(){
 remove-itemproperty -path $Lowriskregpath -name $Lowriskregfile -erroraction silentlycontinue
 }
#EndRegion

Copy and paste that into AutoSPInstallerFunctionsCustom.ps1. Note, that I’m not calling the function anywhere yet, so this by itself won’t even do anything. So let’s call it. For obvious reasons this function should be one of the first things we do, even before installing software. We need to modify AutoSPInstallerMain.ps1 and call this function. In that file, towards the bottom, you’ll see this text:

#Region MAIN - Check for input file and start the install

There’s no need to mess with anything above that really. Find the line “PrepForInstall” and just above it, enter a new line with “Add-LowRiskFileTypes”

That’s it. Now when you run AutoSPInstaller (like normal, using the batch file), this function will be executed.

Customizing

Customizing, for the purposes of this article, means overriding an AutoSPInstaller function, or eliminating it entirely. There may be cases when you either don’t want something to happen or you just need it to happen differently. For the most part, if you want to skip something, you just specify that in your input XML (e.g. parameter=”false”). That is by far the best way to skip something. But not everything is configurable from the XML files so you may need to override it using your own PowerShell function.

Let’s take a look at an example. AutoSPInstaller has a function that will SSL-enable your SharePoint sites (including Central Administration). It’s pretty good! There’s some nice logic in there but it also makes some assumptions that weren’t working for me recently. You can take a look at this function by opening AutoSPInstallerFunctions.ps1 and do a search for “Function AssignCert”

For the most part, I had no problems with the function but it was creating a self-signed wildcard certificate for me based on the last two levels of my domain name. Take this for example:

us.myCompany.corp

The default function will create a certificate with *.myCompany.corp unless it finds one. In my case, I don’t want that because my URLs will include the "us." portion as well, I need a *.us.myCompany.corp certificate (which I have)! My URLs will look something like this: https://sharepoint.us.myCompany.corp/

Now, in my case, my European servers use a domain of eu.myCompany.corp and the good thing for me is that the first “Primary Domain Suffix” on the server matches its domain. This is important for my function since that is what I’m going to use. So here it is:

#Region Assign Certificate
# ===================================================================================
# Func: AssignCert
# Desc: Create and assign SSL Certificate
# ===================================================================================
Function AssignCert([xml]$xmlinput)
{
	ImportWebAdministration
	Write-Host -ForegroundColor White " - Custom: Assigning certificate to site `"https://$SSLHostHeader`:$SSLPort`""
	# Check for sub-domains
 	$NumDomainLevels = ($Env:USERDNSDOMAIN -split "\.").Count
	If ($NumDomainLevels -gt 2) # For example, corp.domain.net
	{
		# Get only the last two (domain + TLD)
		# Commenting this original method out.
		$TopDomain = $Env:USERDNSDOMAIN.Split("\.")[($NumDomainLevels - 2)] + "." + $Env:USERDNSDOMAIN.Split("\.")[($NumDomainLevels - 1)]
		$dns = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.IPEnabled -eq $true} | select 
		$TopDomain = $dns.DNSDomainSuffixSearchOrder[0]
		}
	# If our SSL host header is a FQDN containing the local domain (or part of it, if the local domain is a subdomain), look for an existing wildcard cert
	If ($SSLHostHeader -like "*.$env:USERDNSDOMAIN")
	{
		Write-Host -ForegroundColor White " - Custom: Looking for existing `"*.$env:USERDNSDOMAIN`" wildcard certificate..."
		$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -like "CN=``*.$env:USERDNSDOMAIN*"}
	}
	ElseIf (($NumDomainLevels -gt 2) -and ($SSLHostHeader -like "*.$TopDomain"))
	{
		Write-Host -ForegroundColor White " - Custom: Looking for existing `"*.$TopDomain`" wildcard certificate..."
		$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -like "CN=``*.$TopDomain*"}
	}
	Else
	{
		Write-Host -ForegroundColor White " - Custom: Looking for existing `"$SSLHostHeader`" certificate..."
		$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -eq "CN=$SSLHostHeader"}
	}
	If (!$Cert)
	{
		Write-Host -ForegroundColor White " - Custom: None found."
		# Get the actual location of makecert.exe in case we installed SharePoint in the non-default location
		$SPInstallPath = (Get-Item -Path 'HKLM:\SOFTWARE\Microsoft\Office Server\14.0').GetValue("InstallPath")
		$MakeCert = "$SPInstallPath\Tools\makecert.exe"
		If (Test-Path "$MakeCert")
		{
			Write-Host -ForegroundColor White " - Custom: Creating new self-signed certificate..."
			If ($SSLHostHeader -like "*.$env:USERDNSDOMAIN")
			{
				# Create a new wildcard cert so we can potentially use it on other sites too
				Start-Process -NoNewWindow -Wait -FilePath "$MakeCert" -ArgumentList "-r -pe -n `"CN=*.$env:USERDNSDOMAIN`" -eku 1.3.6.1.5.5.7.3.1 -ss My -sr localMachine -sky exchange -sp `"Microsoft RSA SChannel Cryptographic Provider`" -sy 12"
				$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -like "CN=``*.$env:USERDNSDOMAIN*"}
			}
			ElseIf (($NumDomainLevels -gt 2) -and ($SSLHostHeader -like "*.$TopDomain"))
			{
				# Create a new wildcard cert so we can potentially use it on other sites too
				Start-Process -NoNewWindow -Wait -FilePath "$MakeCert" -ArgumentList "-r -pe -n `"CN=*.$TopDomain`" -eku 1.3.6.1.5.5.7.3.1 -ss My -sr localMachine -sky exchange -sp `"Microsoft RSA SChannel Cryptographic Provider`" -sy 12"
				$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -like "CN=``*.$TopDomain*"}
			}
			Else
			{
				# Just create a cert that matches the SSL host header
				Start-Process -NoNewWindow -Wait -FilePath "$MakeCert" -ArgumentList "-r -pe -n `"CN=$SSLHostHeader`" -eku 1.3.6.1.5.5.7.3.1 -ss My -sr localMachine -sky exchange -sp `"Microsoft RSA SChannel Cryptographic Provider`" -sy 12"
				$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -eq "CN=$SSLHostHeader"}
			}
		}
		Else 
		{
			Write-Host -ForegroundColor White " - `"$MakeCert`" not found."
			Write-Host -ForegroundColor White " - Looking for any machine-named certificates we can use..."
			# Select the first certificate with the most recent valid date
			$Cert = Get-ChildItem cert:\LocalMachine\My | ? {$_.Subject -like "*$env:COMPUTERNAME"} | Sort-Object NotBefore -Desc | Select-Object -First 1
			If (!$Cert)
			{
				Write-Host -ForegroundColor White " - Custom: None found, skipping certificate creation."
			}
		}
	}
	If ($Cert)
	{
		$CertSubject = $Cert.Subject
		Write-Host -ForegroundColor White " - Certificate `"$CertSubject`" found."
		# Fix up the cert subject name to a file-friendly format
		$CertSubjectName = $CertSubject.Split(",")[0] -replace "CN=","" -replace "\*","wildcard"
		# Export our certificate to a file, then import it to the Trusted Root Certification Authorites store so we don't get nasty browser warnings
		# This will actually only work if the Subject and the host part of the URL are the same
		# Borrowed from https://www.orcsweb.com/blog/james/powershell-ing-on-windows-server-how-to-import-certificates-using-powershell/
		Write-Host -ForegroundColor White " - Exporting `"$CertSubject`" to `"$CertSubjectName.cer`"..."
		$Cert.Export("Cert") | Set-Content "$env:TEMP\$CertSubjectName.cer" -Encoding byte
		$Pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
		Write-Host -ForegroundColor White " - Importing `"$CertSubjectName.cer`" to Local Machine\Root..."
		$Pfx.Import("$env:TEMP\$CertSubjectName.cer")
		$Store = New-Object System.Security.Cryptography.X509Certificates.X509Store("Root","LocalMachine")
		$Store.Open("MaxAllowed")
		$Store.Add($Pfx)
		$Store.Close()
		Write-Host -ForegroundColor White " - Custom: Assigning certificate `"$CertSubject`" to SSL-enabled site..."
		#Set-Location IIS:\SslBindings -ErrorAction Inquire
		$Cert | New-Item IIS:\SslBindings\0.0.0.0!$SSLPort -ErrorAction SilentlyContinue | Out-Null
		Set-ItemProperty IIS:\Sites\$SSLSiteName -Name bindings -Value @{protocol="https";bindingInformation="*:$($SSLPort):$($SSLHostHeader)"}
		## Set-WebBinding -Name $SSLSiteName -BindingInformation ":$($SSLPort):" -PropertyName Port -Value $SSLPort -PropertyName Protocol -Value https 
		Write-Host -ForegroundColor White " - Custom: Certificate has been assigned to site `"https://$SSLHostHeader`:$SSLPort`""
	}
	Else {Write-Host -ForegroundColor White " - Custom: No certificates were found, and none could be created."}
	$Cert = $null
}
#EndRegion

This is essentially the same exact function with minor changes. I’m changing the $TopDomain variable to use the DNSDomainSuffixSearchOrder (and taking the top one, the one in position 0).

$dns = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object {$_.IPEnabled -eq $true} | select 
$TopDomain = $dns.DNSDomainSuffixSearchOrder[0]

I’ll admit, this is quite poorly written because there are no checks or traps, but I know my environment and this will work for me.

So, I have this function that I want to use instead of the one packaged with AutoSPInstaller. What’s the right way to override it? Well, its not to just go in and edit AutoSPInstallerFunctions.ps1, no, that file should always be left alone. Instead, add your function to AutoSPInstallerFunctionsCustom.ps1 and just give it the same name. By giving it the same name, it will override the original function.

This is an easy way to just skip certain functions as well. So, lets say I don’t want to check the install account because I am running using the Farm account and that warning message really scares me (don’t run the install using the Farm account, by the way). That’s done using a function called CheckInstallAccount. I’m going to just override it so it doesn’t give me a warning. So here’s my function:

#Region Check Installation Account
# ===================================================================================
# Func: CheckInstallAccount
# Desc: Check the install account and 
# ===================================================================================
Function CheckInstallAccount([xml]$xmlinput)
{
        Write-Host  -ForegroundColor White " - Man! You are so awesome using the Farm account!"
}
#EndRegion

You could even do without the Write-Host (meaning leave it blank) and have it do nothing at all.

In Summary

You can create your own functions and place them in AutoSPInstallerFunctionsCustom.ps1. This file loads after the AutoSPInstallerFunctions.ps1 so any functions with the same name will override the original. If you’ve created functions that you want to run, just add the function name to the AutoSPInstallerMain.ps1 file – in the appropriate place. And finally, its best not to change the AutoSPInstallerFunctions.ps1.

BONUS – Oh Wait, There’s More?

I wanted to give everyone a bonus tip and another example of extended AutoSPInstaller, so here goes!

I’d like to add a bookmark/favorite to Internet Explorer for Central Admin auto-magically. So, I’ve created this function:

#Region Create CA Favorite
# ===================================================================================
# Func: Create-CAFavorite
# Desc: Adds the Central Administration URL to the Favorite bar
# ===================================================================================
Function Create-CAFavorite([xml]$xmlinput) {
#$url = "https://someone.local/test"
$url = $centraladminurl
#Get IE Version
$iepath = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Internet Explorer'
$ieVer = $iepath.Version

If ($ieVer -like "8*") {
	#Favorites Bar location for IE8
	$favdir = "$env:userprofile\Favorites\Links"
}
Else {
	#Favorites Bar location for IE9
	$favdir = "$env:userprofile\Favorites\Favorites Bar"

}

$fav = "[InternetShortcut]
`URL=$url"
$fav | Out-File "$favdir\Central Administration.url"
}
#EndRegion

Just some notes before you guys start screaming and yelling at me. This is only adding the IE Favorite to the profile running AutoSPInstaller (your setup account). And I know I may get some missing links in CA unless I run from the start menu, but I like having this anyway.

So again, that’s in my AutoSPInstallerCustomFunctions.ps1 file. Then I add a reference to it in AutoSPInstallerMain.ps1. By the way, here’s a snippet of that file, the only part that I’ve changed:

Try 
{
	# Some custom functions
	Add-LowRiskFileTypes
	Import-509Certificate # This loads my root CA cert
	Import-PfxCertificate # This loads the wildcard cert for *.us.myCompany.corp

	# Default / orginal functions
	PrepForInstall
	Run-Install
	Setup-Farm
	Setup-Services
	Finalize-Install 

	# Some more custom functions	
	Create-CAFavorite # This creates an IE Favorite for Central Admin
}

Happy SharePoint-ing!

Series NavigationAutoSPInstaller: Execution
1 comment… add one
  • PlateSpinner Jul 1, 2015 Link Reply

    This is what I came here to find: “Instead, add your func­tion to AutoSPInstallerFunctionsCustom.ps1 and just give it the same name.”

    And you provided it, perfectly. Thanks!

Leave a Reply