19 December 2016

Powershell Tip - How to flush the SharePoint config cache

Every SharePoint administrator knows that flushing the config cache becomes necessary from time to time.  This has traditionally been a very manual process whereby the .xml files are deleted from the cache folder and then the cache.ini file's content is overwritten with a single character.
The following script makes that an automatic process.  The script was generated using my GenPSS utility.

##############################################################################
########################## Clear-SPConfigCache.ps1 ###########################
##############################################################################
Description: === DESCRIPTION HERE ===
Author: Cornelius J. van Dyk | http://blog.cjvandyk.com | @cjvandyk
Version: 1.00
Creation Date: 12/19/2016 12:00:00 AM
Licensed under GPL 3.0 http://opensource.org/licenses/GPL-3.0.

Modified by: 
Version: 
Modified Date: 

Instructions:
  1) Simply run this script to clear the SharePoint config cache.
#############################################################################>

##############################################################################
################################# Parameters #################################
##############################################################################
#[CmdletBinding()]
#Param ([Parameter(Mandatory=$true, HelpMessage="PARAMETER 1 PROMPT HERE?")][ValidateNotNullOrEmpty()][string]$PARM1NAME,
#       [Parameter(Mandatory=$false, HelpMessage="PARAMETER 2 PROMPT HERE?")][ValidateNotNullOrEmpty()][string]$PARM2NAME)

##############################################################################
############################### Thread Handling ##############################
##############################################################################
#Optimize memory optimization through thread re-use.
if ($ver.Version.Major -gt 1)
{
    $Host.Runspace.ThreadOptions = "ReuseThread";
}

##############################################################################
############################### Error Handling ###############################
##############################################################################
#Since the script is intended to run unattended, errors should be suppressed.
$ErrorActionPreference = "SilentlyContinue";

##############################################################################
################################### Modules ##################################
##############################################################################
#Remove the module before re-importing it to ensure the Powershell session 
#gets the latest version.
Remove-Module cjvandyk.quix;
Import-Module cjvandyk.quix;

##############################################################################
################################## Snapins ###################################
##############################################################################
Add-pssnapin Microsoft.SharePoint.Powershell -ErrorAction SilentlyContinue

##############################################################################
################################# Transcript #################################
##############################################################################
#Ensure a new recording is taken by calling Stop-Recording first.
try
{
    Stop-Recording;
}
catch {}
#Start the transcript without overwriting any existing file, but appends a
#date/time stamp to the log file name.
Start-Recording;

##############################################################################
################################# Variables ##################################
##############################################################################
# === DEFINE VARIABLES HERE ===
$logFile = ".\Clear-SPConfigCache" + "-" + (Get-Date -Format yyyyMMdd-HHmmss) + ".log"
$cacheFolder = "";

##############################################################################
################################## Functions #################################
##############################################################################
# === DEFINE FUNCTIONS HERE ===
#Log output to screen and log file.
function Log($msg)
{
    $t = Get-Date -Format yyyy/MM/dd_HH:mm:ss
    Write-Host $t $msg
    $t + " " + $msg | Out-File -FilePath $logFile -Append 
}

##############################################################################
####################### Ensure script running as Admin #######################
##############################################################################
if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") -eq $false)
{
    Write-Host "WARNING! Script is NOT executing in Admin mode.  Terminating script." -ForegroundColor Yellow;
    Stop-Recording;
    Exit(403);
}

##############################################################################
#################################### Main ####################################
##############################################################################
#Stop timer jobs.
Stop-Service SPTimerV4;
$folders = Get-ChildItem C:\ProgramData\Microsoft\SharePoint\Config
#Iterate the config folder to find the cache folder.
foreach ($folder in $folders)
{
    $items = Get-ChildItem $folder.FullName -Recurse;
    foreach ($item in $items)
    {
        if ($item.Name.ToLower() -eq "cache.ini")
        {
            $cacheFolder = $folder.FullName;
            break;  #Folder found now breakout of the loop.
        }
    }
}
#Get all files in the cache folder.
$cacheFolderItems = Get-ChildItem $cacheFolder -Recurse;
#Delete only the .xml files in the cache folder.
foreach ($cacheFolderItem in $cacheFolderItems)
{
    if ($cacheFolderItem -like "*.xml")
    {
        $cacheFolderItem.Delete();
    }
}

#Now delete the content of the cache.ini file.
$cacheFile = Get-Content $cacheFolder\cache.ini;
$cacheFile = 1;
Set-Content $cacheFile -Path $cacheFolder\cache.ini;

#Ensure the operator repeats the process on all SharePoint servers.
Read-Host "Please run this script on all your SharePoint Servers BEFORE pressing ENTER!";
#Restart timer jobs.
Start-Service SPTimerV4;

##############################################################################
#################################### End #####################################
##############################################################################
# Stop recording the transcript.
Stop-Recording;


Enjoy
C

21 November 2016

How do I validate verb validity in Powershell?

 So you're about to write a new Powershell cmdlet or script.  What verbs can you use in the name?  Everyone knows you can just do a quick

Get-Verb

Which would dump out something like this:

Verb                                                        Group
----                                                        -----
Add                                                         Common
Clear                                                       Common
Close                                                       Common
Copy                                                        Common
Enter                                                       Common
Exit                                                        Common
Find                                                        Common
Format                                                      Common
Get                                                         Common
Hide                                                        Common
Join                                                        Common
Lock                                                        Common
Move                                                        Common
New                                                         Common
Open                                                        Common
Optimize                                                    Common
Pop                                                         Common
Push                                                        Common
Redo                                                        Common
Remove                                                      Common
Rename                                                      Common
Reset                                                       Common
Resize                                                      Common
Search                                                      Common
Select                                                      Common
Set                                                         Common
Show                                                        Common
Skip                                                        Common
Split                                                       Common
Step                                                        Common
Switch                                                      Common
Undo                                                        Common
Unlock                                                      Common
Watch                                                       Common
Backup                                                      Data
Checkpoint                                                  Data
Compare                                                     Data
Compress                                                    Data
Convert                                                     Data
ConvertFrom                                                 Data
ConvertTo                                                   Data
Dismount                                                    Data
Edit                                                        Data
Expand                                                      Data
Export                                                      Data
Group                                                       Data
Import                                                      Data
Initialize                                                  Data
Limit                                                       Data
Merge                                                       Data
Mount                                                       Data
Out                                                         Data
Publish                                                     Data
Restore                                                     Data
Save                                                        Data
Sync                                                        Data
Unpublish                                                   Data
Update                                                      Data
Approve                                                     Lifecycle
Assert                                                      Lifecycle
Complete                                                    Lifecycle
Confirm                                                     Lifecycle
Deny                                                        Lifecycle
Disable                                                     Lifecycle
Enable                                                      Lifecycle
Install                                                     Lifecycle
Invoke                                                      Lifecycle
Register                                                    Lifecycle
Request                                                     Lifecycle
Restart                                                     Lifecycle
Resume                                                      Lifecycle
Start                                                       Lifecycle
Stop                                                        Lifecycle
Submit                                                      Lifecycle
Suspend                                                     Lifecycle
Uninstall                                                   Lifecycle
Unregister                                                  Lifecycle
Wait                                                        Lifecycle
Debug                                                       Diagnostic
Measure                                                     Diagnostic
Ping                                                        Diagnostic
Repair                                                      Diagnostic
Resolve                                                     Diagnostic
Test                                                        Diagnostic
Trace                                                       Diagnostic
Connect                                                     Communications
Disconnect                                                  Communications
Read                                                        Communications
Receive                                                     Communications
Send                                                        Communications
Write                                                       Communications
Block                                                       Security
Grant                                                       Security
Protect                                                     Security
Revoke                                                      Security
Unblock                                                     Security
Unprotect                                                   Security
Use                                                         Other


Now you COULD painstakingly go through the mass of verbs... oh right, they're NOT in alphabetical order!!!  Well, that's not entirely true.  It is in alphabetical order by GROUP.  Who gives a damn about Group?!?  Ugh.
OK, so we could also just pipe the output to the sort command thus:

Get-Verb | sort Verb

That will result in properly sorted output which would make it easier to locate (or not) the intended verb in the list thereby determining if the verb is valid for use according to best practice guidance. :-)
But all that is a terribly manual process and as anyone who knows me will tell you, once I have to repeat a tedious process for a second or third time, I look for a way to automate it.
That's exactly what I did here too.  Presenting...

Get-ValidVerb

This is my little function to do the above quickly and easily.  It can easily be compiled into a module that's loaded in your profile so you'll always have access to it.  Here's the code:

function Get-ValidVerb([string] $verbToCheck)
{
 $verbs = Get-Verb | sort Verb;
 $list =  New-Object -TypeName System.Collections.Generic.List[string];
 foreach ($verb in $verbs) 
 { 
  $list.Add([string]$verb.Verb.ToLower());
 }
 return $list.Contains($verbToCheck.ToLower());
}

The script logic is easy.

  1. The script simply takes a single string argument containing the verb to check for validity.
  2. In line 3 we capture the output from our (Get-Verb | sort Verb) command to a variable.
  3. In line 4 we create a .NET List object that contains strings.
  4. In line 5 we iterate through all items in the dumped list.
  5. In line 7 we use the .Add() method of the List class to add only the Verb of the item (remember, it has a Group column too) to the list.  I added another piece of logic here in that I convert the verb to lowercase with .ToLower() after casting it as a [string] type.  This is done in order to allow the next bit of logic...
  6. Finally in line 9, we leverage the .Contains() method of the List class to check if the verb we're checking, is contained in the list.  You'll notice that we cast this verb to check to lower case as well.  By casting both the list and the verb to check to lower case, we get a case insensitive comparison.  This prevents the problem where Get-ValidVerb "get" will return False because of case different in the G.

Enjoy
C







24 October 2016

RIP Oscar (2003-2016)

He was my good boy, my companion for 14 years.  He never asked for much except for some loving belly rubs and the occasional ball to chase down the hall.  He was no saint, forcing me to move our gutters in front of the back yard fence.  There was not a gutter safe when Oscar thought there might be a chipmunk hiding in there!  He was no saint, but he was my good boy.
At times he could let out the stinkiest gas you've ever smelled, but have him sleeping on your lap and you let one out... he'd look at you all disgusted and then hop down and walk away in a manner of disgust that only Oscar could pull off.
He was always with me, ever present.  Our morning routine was "Let's go to work boy!" and he'd trot along with me down to the basement and lay on his pillow next to my desk all day long.

Like so many things in life, his time was too short.  We laid him to rest yesterday morning and the grief still wells up behind my eyes frequently.  I'm going to miss him so much, my good boy.
Thank you Oscar.
Thank you my good boy for 14 great years of selfless love and joy.


Oscar having ice cream.


Oscar belly deep in the basement flood.


Ready to work dad!


I'm exhausted!


I have no idea where that came from dad.


Hey dad, you stopped scratching my belly.


I love my Ouma!


Boy I love my naps.


Ahhh, the fruits of my labor...


Whacha talkin 'bout dad... hole, what hole?  You can't prove a thing!


Let me at 'em dad!  I'll get that chipmunk!
https://goo.gl/photos/kosNBQUewRYkzKbk7

Of course, his #1 favorite was to kill squeaky toys!
https://goo.gl/photos/pc79D6kyeMPZCY3A6


Thank you my good boy.
Thank you for 14 wonderful years.
THANK YOU!!!

17 October 2016

Powershell - Super commands [Invoke-WebRequest]

If you've been playing around with Powershell modules, you've undoubtedly run into the problem of keeping your modules, deployed all over the place on multiple servers, up to date with the latest version.  Powershell scripts and modules don't normally fall under traditional application life-cycle management (ALM) processes, so unless you/your organization apply targeted and specific effort toward managing these objects via ALM, you know what a pain mismatched versions can be.  We can save the debate over ALM for another day, but the simple fact is, that keeping scripts and modules updated can become tedious.
In my world, I've addressed this problem through the use of an updater module and the very powerful cmdlet called "Invoke-WebRequest".

Invoke-WebRequest is documented here:
https://msdn.microsoft.com/en-us/powershell/reference/4.0/microsoft.powershell.utility/invoke-webrequest

I built a command line app to generate my Powershell script body with all the components I normally use.  Part of these components is the loading of the updater module.  Once the script has checked that it's running with admin rights and the updater module is loaded, the method in the updater module is called to ensure that the main module is up to date with the latest version.  The updater module simply makes a call to Invoke-WebRequest like this:

C:\> Invoke-WebRequest -Uri http://www.sharepointmvp.net/ulsviewer.zip -OutFile C:\data\UlsViewer.zip

The above example simply pulls a zipped copy of ULSViewer down from the internet, so you can test it for yourself.  In my case it looks something like this:

C:\> Invoke-WebRequest -Uri http://master-server/Utils.psm1 -OutFile C:\Program Files\WindowsPowerShell\Modules\Utils.psm1

This will update the module which is then loaded into the script to provide all the latest features of the script.  Now it's important to understand the potential security risk of doing this.  The master-server location of the module has to be well protected and tightly controlled.  Failure to do so, could result in someone planting some malicious script code into your module and then distributing it to all your servers.  Remember that convenience and security are polar opposites.  When you increase one, you necessarily decrease the other.

Nevertheless, through this implementation, I am able to manage a central source module for my helper methods that is then re-used in all my scripts.  Fixing a bug once, fixes it everywhere.  Conversely, breaking a method in the module once, also breaks it everywhere. :-P

I'm sure you can come up with many more useful ways of using Invoke-WebRequest.  Let me know in comments about some of your favorite ways to leverage this powerful cmdlet.

Later
C

19 September 2016

Powershell Power Boost

If you're new to Powershell or if you've been around the block a few times but have no background in C#, then you may benefit from the advantages of using C#.NET's List classes.  The class is fully documented here:

https://msdn.microsoft.com/en-us/library/6sh2ey19

Take a look.  There are multiple pre-built methods that come in very handy with Powershell scripts.  Some of my favorites are Count(), Contains(T), Reverse(), Sort() and many more.

Leveraging lists is easy once you've defined them in Powershell thusly:

$list = New-Object -TypeName System.Collections.Generic.List[String];

So go ahead and give List a try in your scripts... you'll never use arrays again!

Later
C

15 August 2016

How do I - Create a SharePoint 2013 sub site template when the UI option is not there?

SharePoint's publishing infrastructure and Features have been the bane of many a debug session I've been involved in.  SharePoint sites just behave differently when publishing is turned on.  Along with this, comes some menu and settings differences that Microsoft enforces for publishing sites.  Good, bad or indifferent, these nuances require some unique workarounds from time to time.

I have a client who is using publishing as part of their base site template.  Now the client wants to create a site template from a given sub site.  The template would then be used to create other sub sites in the same site collection.  Without publishing, this is no problem as you simply navigate to the Settings Gear > Site Settings > Site Actions > Save as template.  When publishing is turned on however, this option disappears from the settings page.
No problem you think.  I'm a smart and attentive SharePoint guru and I know what the "Save as template" page's URL is.  I'll just manually type it into the browser's address bar.  So you proceed to manually add _layouts/15/savetmpl.aspx to the current site URL so it would read something like this:
http://www.crayveon.com/sites/PublishingSite1/SubSiteA/_layouts/15/savetmpl.aspx
With confidence you press the key on your keyboard and...

















This is one of those little settings Microsoft made while trying to "protect" you from yourself.  OK, so how do we work around this one?
It's actually pretty easy once you know how the underlying process functions.  The "savetmpl.aspx" page does a lookup at the site property bag to see if there's a specific variable defined.  The variable is called "SaveSiteAsTemplateEnabled".  If it's defined, its value is checked and if set to "false", then the above error message is thrown and the template save process is aborted.
That said, we can work around this by using SharePoint Designer to change the variable value.  Here are the complete steps to overcome this little problem:
  1. Open SharePoint Designer 2013. 
  2. Open your target site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA  
  3. In the ribbon at the top, on the far right, click "Site Options". 
  4. On the Site Options dialog, under the "Parameters" tab, you will notice the target variable in question... "SaveSiteAsTemplateEnabled".  Its value should read "false".  Select the variable and click the "Modify" button on the right. 
  5. Change the value to "true" and click "OK". 
  6. Click "OK" to close the Site Options dialog window.
  7. From your browser, navigate to the target site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA
  8. From the target site, add "/_layouts/15/savetmpl.aspx" to the URL of the site e.g. http://www.crayveon.com/sites/PublishingSite1/SubSiteA/_layouts/15/savetmpl.aspx and press Enter.
  9. You should be presented with the Save as Template page. 
  10. Fill out your desired options and click "OK" to save the template.
Provided you don't have any custom apps on the site that are not compatible with templatization, the result should be successful.  If you do have custom apps, you'll have to remove said apps and then try saving as a template again.

Enjoy!
C




18 July 2016

How do I - Exclude certain file types from a XCopy folder sync?

When you're managing multiple SharePoint farms that rely on custom, in-house developed Powershell modules, keeping these modules up to date across the entire enterprise can be a tricky prospect.  This is usually where I employ some Powershell with calls to XCopy.

XCopy is a very powerful tool for synching files between systems.  As you can see from its documentation page, there are many command switches for it.  The tricky thing is usually when you're trying to sync folders that contain Powershell scripts, but these scripts generate log files.  You want to sync the .ps1 files, but not the .log files.  How do we do that then?

This is where the /exclude: switch comes into play.  You can create a text file containing the extensions you wish to exclude.  Then you simply pass the text file to the /exclude: switch thus:

xcopy S:\Corne\Scripts\*.* T:\Scripts /E /I /F /H /R /Y /d /exclude:Exclusions.txt

Its important to remember that the exclusion will do a simple path match on the values specified in exclusions.txt.  As such, having the file content defined thus:

.log

will exclude anything that matches ".log" somewhere in the path of the file e.g. files ending in ".logger", ".logs", ".log2, ".log" etc. will all be excluded from XCopy.  This can be resolved by simply adding a backslash to the back of the extension thus:

.log\

Now XCopy will only ignore files with a ".log" extension.

Enjoy
C

18 June 2016

Powershell Tip - Padding Text

Since we use Powershell for just about everything these days, you're sure to end up generating some kind of quick report of something in SharePoint through a simple Powershell script.  Inevitably, the need to pad text comes up.  Powershell makes it easy to left and right pad text variables through the use of the .PadLeft() and .PadRight() .NET methods used as follows:

$str = "My Report Title";
$strPad = $str.PadLeft(20) + ".";
Write-Host $strPad;

The output looks like this:

     My Report Title.

In this case, we were left padding the string to 20 characters.  By default, padding is done with spaces so given the string was already 15 characters long, 5 spaces were added to the front of the string resulting in our output.  The use of the additional period will become apparent as we look at right padding the text as follows:

$strPad = $str.PadRight(20) + ".";
Write-Host $strPad;

The output looks like this:

My Report Title     .

Here you can clearly see the 5 spaces before the period. ;-)
If you want to pad with something other than spaces, you can simply supply the second, optional parameter to PadLeft() or PadRight() specifying the character to be used for padding e.g.

$strPad = $str.PadRight(20, "$") + ".";
Write-Host $strPad;

The output looks like this:

My Report Title$$$$$.

Enjoy!
C


22 May 2016

How do I - Round numbers to a given decimal space in Powershell?

One of my subscribers asked how to round in Powershell.  It's actually quite easy.  All you have to do is use the [math] preprocessor's Round() method e.g. if I wanted to round to two decimal places I'll use the following:

[math]::Round($value, 2);

If I issue the Round() call without a second parameter thus:

[math]::Round($value);

I would get an integer rounded value, i.e. I'd get the whole, ignoring any decimals.

Hope that helps!

Later
C

16 April 2016

How do I use Powershell with IIS 7 or later?

If you're into any form of automation, you know that Powershell rocks!
That said, the real strength of Powershell comes from it's snap-ins.  These extensions allow you to do all manner of things.  One of those is the ability to script actions in IIS.  To get started you'll need to ensure you have the Feature installed on your flavor of Windows.  If you don't have it installed, you can either download it here:

https://www.microsoft.com/en-us/download/details.aspx?id=7436

or add it via Windows' Programs and Features.  Generally on anything late Windows 7 or Server 2008, it's already loaded.

Once installed, you'll need to load the snap-in thus:

Import-Module WebAdministration
From here, you're only limited by the available calls and your imaginations.

Happy scripting.
C

10 March 2016

What is the SharePoint hive?

Almost a decade ago I posted an article explaining what the 60/12 hive is.  Since then, SharePoint has undergone many new versions so I thought it was time I updated my hive article just a bit to include the three additional major releases since then.  For more information on the origination of the "hive", refer to the original article linked above.

SharePoint Version
Hive Server Disk Location
2007C:\Program Files\Common Files\Microsoft Shared\web server extensions\12
2010C:\Program Files\Common Files\Microsoft Shared\web server extensions\14
2013C:\Program Files\Common Files\Microsoft Shared\web server extensions\15
2016C:\Program Files\Common Files\Microsoft Shared\web server extensions\16

Enjoy
C

22 February 2016

How do I - Get a list of email addresses of all users who belong to a given SharePoint security group?

One of mentees asked me how to extract the emails of all the owners of a given site.  With some Powershell magic and the use of the -ExpandProperty switch, this is actually quite easy.

$url = "http://{webapp}/sites/{site}";
$owners = Get-SPWeb $url | Select -ExpandProperty SiteGroups | Where {$_.Name.ToLower() -match ((Get-SPWeb $url).Title.ToLower() + " owners")} | Select -ExpandProperty Users | Select Email;
foreach ($owner in $owners)
{
   {do something with $owner.Email}
}

In the above script, we start off by setting the $url variable.  Then we make the big call to get the owners list of emails into $owners.
The command calls Get-SPWeb for the given URL.  The return of that is then piped to a Select command using the -ExpandProperty switch to expand the SiteGroups.  This would get you all site groups, but piping it to the Where command allows us to filter the returned groups down to the one we want.
The Where command is fed the name of the site group (in lower case) and is asked to match it against the spweb object's title (in lower case) with " owners" concatenated.  A couple of things to note:

  1. It is good practice to ALWAYS make the case of text the same on both sides of a text based comparison.  I prefer lower case, hence the dispersion of .ToLower() in my code.
  2. This part of the code does depend on default security groups being used in SharePoint.  This is 95% of the time the case, but you should be aware that different naming conventions may have been used for the name of the site owners group.
Now that we have the owners security group, the result is piped to another Select command expanding to return the users of that group.  Finally, we pipe to a Select statement that selects a single field i.e. the email address.

You now have an array of email addresses that can be iterated and used via the .Email field reference.

Enjoy
C

18 January 2016

How to load a dot net class in Powershell when you don't know the DLL name.

Most of us know that the main SharePoint classes live in Microsoft.SharePoint.dll.  The easiest way to load this assembly is by using the ::LoadWithPartialName method thus releasing you from having to use the exact DLL name.  In this case its obvious, but there are a couple of classes that are in DLLs that are not so obvious.  Simply call the following:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint");

You can replace the "Microsoft.SharePoint" with any class name, provided its accessible on the system.

Enjoy
C

SharePoint Remote Event Receivers are DEAD!!!

 Well, the time has finally come.  It was evident when Microsoft started pushing everyone to WebHooks, but this FAQ and related announcement...