Author Archives: Wouter Bleeker

Working with Get-CacheClusterHealth and judge the results in scripts/checks

The AppFabric Distributed Cache Service is an important yet fragile part of SharePoint 2013. To get the last status and for example use it in your monitoring might be a wanted solution to get a trigger when something is wrong with it.
The command “Get-CacheClusterHealth” gives a lot of textual results about the current state of all caches and cachehosts, which can be good, but is difficult to use and to interpret. The result is of type “Microsoft.ApplicationServer.Caching.Commands.ClusterHealth”.
The formatting is text, not objects as we are used to in Powershell. Furthermore, the structure is not consistent as such to easily convert to objects, the hostname is mentioned once and then all NamedCaches are listed without referencing the hostname again.

The output is explained in this Microsoft article: technet.microsoft.com/en-us/library/ff921010.aspx

The result is nicely described in this table on referenced TechNet article:

Health Category Description
Healthy The cache is operating normally. This is the target state for all caches.
UnderReconfiguration The cache is under reconfiguration. This is an internal state that may have several causes, but it should be temporary and resolve to healthy.
NotPrimary The cache is not currently available. This can happen when secondary copies are promoted to primary. During this transition, the cache may temporarily have a state of NotPrimary. This state should typically resolve to healthy.
NoWriteQuorum The cache is read-only, because the cache is unable to create the required number of replicas on secondary cache hosts. This occurs when the cache has the high availability option enabled (Secondaries = 1). In this scenario, there must be at least two running cache hosts in the cluster, one for the primary copy of the cached item and another for the secondary copy.
Throttled The cache is read-only, because the cache host is in a throttled memory state. This is a low-memory condition.

 

The actual output looks like this (fragment):

 

Writing code to convert the result

I couldn’t find a working solution to handle the data, so I wanted to create my own. Its based on regular expressions. My inspiration comes from this example: msgoodies.blogspot.nl/2008/12/matching-multi-line-text-and-converting.
It took me some time to get the regex working, but I finally found a way in which the CacheClusterHealth can be used in an automated script that can regularly check the Health and trigger an alert if anything is wrong.

In each line of output, the “property name” is stated before the value, like “HostName = SP2013FE2.local.nl” and “Healthy = 5,00”, in the end result we want to have each property associated to each NamedCache per Host. That last part needed some more fine-tuning.

HostName NamedCache Healthy UnderReconfiguration NotPrimary InadequateSecondaries Throttled
 SP2013Fe  DistributedSecurityTrimmingCache_<<GUID>>  5  0  0  0  0
 SP2013FE  DistributedSearchCache_<<GUID>>  5  0  0  0  0
 SP2013FE  DistributedViewStateCache_<<GUID>>  5  0  0  0  0
 …  …
 SP2013FE2  DistributedSecurityTrimmingCache_<<GUID>>  5  0  0  0  0

The script: regex

The first part consists of the regex, it has 2 blocks surrounded by round brackets, the difference between the 2 is that the first one is with hostname and the second without. That’s because we first encounter a namedcache that is preceded by the hostname, and after that the other namedcaches are listed for that same hostname without referencing the hostname again.

In this regex we lookup each property by it’s name and the equal sign, the \s+ is used as “one or more whitespace characters”. The actual value is extracted using /S+ which means “one or more characters of anything but whitespace type”. We make sure that the result includes each property name enclosed in the lesser-than and greater-than signs, which will be used in the Powershell Object creation.

 

 

The script: matches to objects

The next part handles the results of the regex matches, important is we group by hostname because we only get the hostname once for all caches that are related to that hostname, so we safe it in the $hostname variable.

 

Examples to determine the health based on the script

Update: CAUTION: This next part is fundamentally wrong!

 

The last part consists of some examples to use the data:

 

The end result might look like:

 

Active Directory GUID to Friendly Name using just Powershell

Let’s drill down to the methods we can use within Powershell to retrieve Active Directory permissions and translate the GUID:

  1. How can we get the name of a property or (extended) right in the Active Directory that a specific user has on a specific OU (path)?
  2. How can we do this without having to install anything on our server, like Active Directory Remote Management Powershell Extensions?
  3. How can we do this without using .NET code and without features/extensions?

How can we get the name of a property or (extended) right in the Active Directory that a specific user has on a specific OU (path)?
The first question could be answered with the advise to use the QAD Powershell cmdlets, specifically the cmdlet Get-QADPermission. An alternative was to use Get-ACL on Active Directory objects, but this needs the ActiveDirectory Powershell Module (Add Feature: “Active Directory module for Windows PowerShell” at “Remote Server Administration Tools” > “Role Administration Tools” > “AD DS and AD LDS Tools”).

How can we do this without having to install anything on our server, like Active Directory Remote Management Powershell Extensions?
The second question lead me the way to this book: The .NET Developer’s Guide to Directory Services Programming, through this link http://flylib.com/books/en/1.434.1.83/1/. The code is found at “Listing 8.4. GUID-to-Friendly-Name Conversion” and the BuildFilterOctetString from “Listing 4.2. Converting Binary to String for Search Filters”.
We could include .NET code in our Powershell script/function/module, this requires to add the .NET code from the book using the here-string method and use the functions in that .NET code in our Powershell script.

I implemented this using the code from the book and a reference to the DirectoryServices assembly:

The key functions are:

How can we do this without using .NET code and without features/extensions?
This meant I had to find or build the right Powershell code to do the same as the .NET code above. It seemed difficult but the key was to follow the .NET code and replicate it in Powershell.

The basics are:

 

 
Our context for which we needed this:

In our customer environment we are installing SharePoint which relies heavily on several OS configurations and permissions. We have built an entire Powershell script for the automated installation and configuration of the whole A to Z process including OS firewall and updating AppFabric. The installation script needs to be able to run smoothly and the end result needs to be the same for every environment (all-in-one development image on laptop, 3 server set-up, 8 server set-up, etc.). We do not want to install anything (OS Roles/Features, third party tools) on our servers that is not used by our application (SharePoint/SQL/OfficeWebApps/ADFS/etc).
Since the administration of OS, AD, policies, rights, etc is done by others (except for our laptop) and mistakes happen, we script checks to test all conditions, permissions, settings and versions.
One of these checks is to test the required rights in the Active Directory. We have a password reset and password change webpart, which require specific Active Directory permissions, like “User-Force-Change-Password”.

We can acquire an access rule collection for the LDAP Path to the User Accounts OU in this way:
(New-Object System.DirectoryServices.DirectoryEntry(“LDAP://$UserPath”)).PSBase.ObjectSecurity.Access

This will return an array of ActiveDirectoryAccessRule objects, like this:
ActiveDirectoryRights : CreateChild, Self, WriteProperty, ExtendedRight, Delete, GenericRead, WriteDacl, WriteOwner
InheritanceType : All
ObjectType : 00000000-0000-0000-0000-000000000000
InheritedObjectType : 00000000-0000-0000-0000-000000000000
ObjectFlags : None
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited : True
InheritanceFlags : ContainerInherit
PropagationFlags : None

Above corresponds to “All” objects and properties. In case of specific permissions, the ObjectType and InheritedObjectType values are GUID’s for the property, object or special permisson.

In our case, we could expect something like this:
ActiveDirectoryRights : ExtendedRight
InheritanceType : Descendents
ObjectType : 00299570-246d-11d0-a768-00aa006e0529
InheritedObjectType : bf967aba-0de6-11d0-a285-00aa003049e2
ObjectFlags : ObjectAceTypePresent, InheritedObjectAceTypePresent
AccessControlType : Allow
IdentityReference : DOMAIN\AccountManagement
IsInherited : False
InheritanceFlags : ContainerInherit
PropagationFlags : InheritOnly

Somehow we want to translate these GUID’s to something useful and understandable.

The functions we created with our Powershell code to find and translate GUID’s to rights:

Using a directory array for a choice menu inside a function

Usage of [System.Management.Automation.Host.ChoiceDescription[]] and $host.ui.PromptForChoice() with an array of data for choices.
Default behavior when using functions is that the return value is every output, so when you want to return a value, using Write-Host inside the function will mess the return value up.
The scenario is I wanted the user to provide a name, which should match a directory with configuration, when the name is not provided I want to provide all directories as options.

To provide a simple and familiar choice menu, I use the PromptForChoice function. A choice is defined in this way: New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Do not carry out this action". All choices must be declared with an array inside [System.Management.Automation.Host.ChoiceDescription[]].

First I determine my script directory in this way:
$InstallerLiteralPath = ([System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition))
This works both for local and network UNC paths.

Next I determine the subdirectories, we need this as an array because we need the count attribute, so enclose it with @(…):
$ApplicationDirs = @(Get-ChildItem "$InstallerLiteralPath\configuration" | ?{ $_.PSIsContainer } | Select-Object Name)

Considering I want the choices to be subdirectories of a predefined directory, this proved to be a little bit tougher.
The PromptForChoice function is build up of a Title, Description, the choices, and the default choice.
We need a Cancel option also, which we want to be the default choice, so the choice array needs a cancel option and the default choice needs to be the number of directories, because the default choice count starts at zero and the cancel option is an extra choice.

Set up the Title and Description:
$caption = "Application Selection"
$message = "$($message)The following application configuration directories were found.rnWhich application would you like to configure/install?"

Set up the choices with the directory name as choice option for each directory plus the Cancel option:
$choices = [System.Management.Automation.Host.ChoiceDescription[]]@(($ApplicationDirs | %{$_.Name});"&Cancel")

Set up the default choice:
[int]$defaultChoice = $ApplicationDirs.Count

Display the Choice Menu with the variables defined above, $choiceRTN is the returned choice number the user has entered:
$choiceRTN = $host.ui.PromptForChoice($caption,$message, $choices,$defaultChoice)

Exit with a throw when the default choice (cancel) is chosen.
If ($choiceRTN -eq $defaultChoice){
Throw "[$($myInvocation.MyCommand)] : Application selection cancelled by user."
}

The directoryname of the chosen folder can be determined by the choice number in the array of directories:
$SelectedApplicationFolder = $ApplicationDirs[$choiceRTN].Name

The whole function would look like this:

 

Disable and set result for SharePoint 2013 Health Analysis Rules and Reports

I wanted to disable the SharePoint Health Rules in my Powershell script but it seemed the corresponding Health Report Item was not effected by the disable cmdlet.

While working out how Microsoft has implemented this functionality, I thought let’s share some details and the function that sets the Health Rules and the Severity of the Reports List item.

Some ways of getting the possible values of the Health Rules:

The choices that are available:

  • 0 – RuleExecutionFailure
  • 1 – Error
  • 2 – Warning
  • 3 – Information
  • 4 – Success

The Health Rules (Get-SPHealthAnalysisRule) and the Health Reports List (SPHealthReportsList) Items can be linked by the Rule Summary and the Report Item Name, another option is the value for the Field “HealthRuleType”. In the last case you need to get the rule from the internal SharePoint Rules List (SPHealthRulesList):

My function that takes an XML as input and disables the SharePoint Health Analysis Rules and sets the value in the Health Analysis Report List:

The corresponding XML part:

 

Conditional Where Clause in Powershell

Switching between ‘match‘ and ‘eq‘ in a where clause, depending on a variable:

 

Hide Webpart for Anonymous users, show only to logged in users

When searching for a simple and elegant solution to hide a webpart for anonymous users and show it only to logged in users, I couldn’t find a Powershell way to do it.

Though it was quite easy in retrospect when I found the CSharp code and explored the possibilities of the out of the box Audience.

AudienceManager audienceManager = new AudienceManager(ServerContext.Current);
webPartManager.WebParts[0].AuthorizationFilter = string.Format(“{0};;;;”, audienceManager.GetAudience(“Audience 1”).AudienceID);

The powershell way:

 

Of course you need a Web, a Page and a WebPartManager and  then create a new webpart or get an existing webpart.

A working example where a new webpart is added on the welcomepage:

 

Add-SPShellAdmin: Cannot add x to the SharePoint_Shell_Access role

Have you seen this error before in one form or another while installing or administrating SharePoint 2013?
Found solutions saying to change database settings/accounts?
Here is the solution that does not break anything and keeps everything in the correct configuration on your farm and databases. The only problem is security, but this might be OK when it’s only temporarily. It seems the best option when using scripted installation and a single script creates the databases and adds the ShellAdmin rights to Application Pool accounts for instance.

The error I expect you have had:

Add-SPShellAdmin :
“Cannot add domain\portal-apppool-service-account to the SharePoint_Shell_Access role of the database prefix_Profile. A possible cause of this error is that the account name was already added to the database as a login using a different user name than the account name.”
At Drive:\InstallDir\SP2013\AutoSPInstaller\AutoSPInstallerFunctions.ps1:3217 char:67
+ Get-SPDatabase | ? {$_.Name -eq $profileDB} | Add-SPShellAdmin …
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (Microsoft.Share…AddSPShellAdmin:SPCmdletAddSPShellAdmin) [Add-SPShellAdmin], ArgumentException
+ FullyQualifiedErrorId : Microsoft.SharePoint.PowerShell.SPCmdletAddSPShellAdmin

The solution for problems during installation and configuration:

Grant your current account (install/setup, administrator or IT-Pro manage account) the SQL Sysadmin role.
After this, you can have SharePoint follow up on the Add-SPShellAdmin activities. So re-issue the Add-SPShellAdmin command and voilà. Afterwords remove the sysadmin role in SQL.

The solution when trying to give SPShellAdmin rights to your SP install account:

Change the db owner of the affected SharePoint database.
If somehow the account you want to give ShellAdmin rights is configured as owner with username dbo and the loginname you are trying to use in the Powershell command, it will not work either way. This only happens with the install account.

dbo user name in SQL, with SP Install account as login name.

dbo user name in SQL, with SP Install account as login name.

In this case, the message is really correct, the account was already added to the database as a login using a different username, as the dbo username. You can run this SQL Query, which only affects rights and does not violate the Microsoft Guidelines. After this, you can have SharePoint follow up on the Add-SPShellAdmin activities. So re-issue the Add-SPShellAdmin command and voilà.

Use database_name
Exec sp_changedbowner 'sa'

 

In my case, the problem occurred during the AutoSPInstaller installation of my SharePoint farm, with adding the Application Pool account of my Portal WebApplication the ShellAdmin rights to my User Profile Service Application -Profile database. A generic script which solves the issue, once the SQL role is added to the setup account which you are using to correct the issue, can be along the lines of:

#This script works on On-Premise environments with 2 or more WebApplications, of which one WebApplication is dedicated for MySites.
You can also define the account manually and run it like this: Add-SPShellAdmin -UserName "domain\username" -Database ( Get-SPDatabase | ?{$_.Type -eq "Microsoft.Office.Server.Administration.ProfileDatabase"} )
 

 

A nice explanation on what Add-SPShellAdmin actually does can be found on this site: http://andersrask.sharepointspace.com/Lists/Posts/Post.aspx?ID=12.

Failure checking user access permissions for User Profile Application Proxy

Oh, and the error when the shelladmin rights are not provided to the portal application pool account on the User Profile – Profile database will cause these errors (found in SharePoint ULS Logs) when opening your Search Center:

SearchUXMonitoredScope::OnDisposing System.Data.SqlClient.SqlException (0x80131904)
Application error when access /SearchCenter/Pages/default.aspx
Failure checking user access permissions for User Profile Application Proxy ‘User Profile Service Application’.
System.Data.SqlClient.SqlException (0x80131904): The EXECUTE permission was denied on the object ‘Admin_GetPartitionProperties’, database ‘prefix_Profile’, schema ‘dbo’.     at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
Getting Error Message for Exception System.Web.HttpUnhandledException (0x80004005):

Loading VisualBasic code in Powershell to find out I didn’t need it

Working in a multi domain environment, I needed the NETBIOS name of the server domain. I eventually got a very simple VisualBasic code block to work in Powershell, but found out it was not what I was looking for. A differentiation did give me what I needed but it proved to be a method not worth using.

I was working on different ways to determine the NETBIOS DomainName of a Windows server (in the server context, not the user context).
I found some information using [ADSI] (Active Directory Service Interfaces) in Powershell, but I also found a reference to WinNTSystemInfo, but using vbscript.

It seemed to me that this VBScript object should be easy to (re)create in Powershell. It looks like I didn’t got that part right.

When I finally found a way to get the domain name out of the WinNTSystemInfo object using VisualBasic inside Powershell, it provided me with the Domain in the User context

 

The same page I found also referenced the ADSystemInfo VB Object, so I gave that a try with the same Powershell Add-Type method.

This time it did give me the server domain NETBIOS name, this is the code that I got working:

 

In the end I have found another reference, which included the WMI object Win32_NTDomain, again a VBScript / VisualBasic scenario, but using WMI is easy in Powershell:

This could give numerous domains, because in the case the user is from another domain than the server, it gives both. I haven’t found a way to determine which domain is the user domain, but you could use a Where clause, like (Get-WmiObject Win32_NTDomain | ?{$_.DnsForestName -like ((Get-WmiObject Win32_ComputerSystem).Domain) }).DomainName, or maybe this [DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Forest (or GetComputerDomain().DomainControllers.Forest).

Using ADSI it would be this using Powershell:

 

This does provide the NETBIOS DomainName in the server context.

 

So keeping it to Powershell without VisualBasic is much easier.

 

SPTrustedIdentityTokenIssuer URI invalid

This week a problem occured on our SharePoint environment due to human error, but with unexpected SharePoint behavior driving the cause and troubling the solution.
Somehow it is possible to update the SPTrustedIdentityTokenIssuer property ProviderSignoutUri with an invalid value, and all subsequent repair actions will fail, as well as many SPTimerjobs. All give the same error “Invalid URI: The format of the URI could not be determined”.

 

The SPTrustedIdentityTokenIssuer cannot be deleted or altered (while it’s in use), the WebApplications cannot be altered or updated (and they use the SPTrustedIdentityTokenIssuer), the timerjob RefreshCache fails everytime, even solutions cannot be installed or uninstalled.

We found out that, although the .Update() action triggered an exception, the attribute does get changed in the database. When displaying the properties of the SPTrustedIdentityTokenIssuer however, the attribute seemed empty. This made us go on a wild-goose chase, until we checked everything again and determined that the problem didn’t pre-exist and it had to be related to the SPTrustedIdentityTokenIssuer object.
So I dug a little deeper and decided to look in the SharePoint config database:

In the resulting row, the properties column contains a large XML with all configuration related to your TrustedIdentityTokenIssuer.

Here we found out that the attribute “ProviderSignoutUri” was populated with a value, the human error had somehow appeared in the SharePoint Configuration Database, although it wasn’t visible with the Powershell command Get-SPTrustedIdentityTokenIssuer, it appeared empty! This left us with no other choice than to change the value with SQL (UNSUPPORTED). Make a back-up of your database before you attempt this fix. Make sure you copy the whole XML to your UPDATE statement and only change the row with the Id of the TrustedIdentityTokenIssuer!

 

When the SQL UPDATE statement has been executed, update the object from Powershell commandline without any alteration and you are good to go again.

So, SharePoint allows us to change the attribute to an invalid value, gives us an exception about it, but changes it in it’s SharePoint Configuration Database, and furthermore wont show the actual value and won’t let us change anything related to it. Don’t want anyone to have to go through all this.

 

 

 

 

For future reference, here are some details of the exceptions and the logfiles.

The ULS logfile entries of the first occurrence and the resulting timerjob failure:

Saving passwords securely in Powershell

When dealing with passwords in unattended installations, the challenge is always if the password is securly stored.
One way to deal with it is to use “Read-Host -AsSecureString” on the target server from within the script.
But when dealing with a lot of passwords in 1 script, this will cause a lot of prompts.
You could save every password with the ConvertFrom-SecureString function, the default uses a User specific masterkey (through Data Protection API – DPAPI) so it can only be used when the script is run as the same user. Also it is easy to get the password through reversing this as that same user.

Get plain password from securestring:

Or

 

To overcome both problems we need something better. This is where a custom key can be used with the ConvertFrom-SecureString function. The key must be in byte format and must be 16, 24 or 32 characters long.

Then you only have to ask once for a key and you can use that to decrypt all passwords that are stored in the script or in configuration files.

An alternative to this manually defined key is using a random 32bit key:

This random key is not a solution in itself, because you can’t remember it, so we need to provide this key and we can use a certificate, which provides encrypt and decrypt options of the previously used key. When encrypting the key with a specific certificate, we can provide the key to anyone who has the certificate that is needed to decrypt this key, after which we can use the decrypted key to decrypt the passwords.

In this case the steps would be:

  1. Create byte key (random generated, or a specific passphrase)
  2. Make securestrings of passwords
  3. Convert (encrypt) securestrings with key
  4. encrypt key with certificate
  5. provide encrypted key (or have it entered when its a passphrase) and encrypted passwords
  6. decrypt key with certificate
  7. decrypt passwords with the key

Read more on: