RSS

How do I - Change a SharePoint site URL?


This is probably one of the most common questions I've been asked over the years.  Unfortunately, Microsoft doesn't provide a UI method for users to change the URL of their site.  Given the database schema and the relationships between objects and the URL, I've always been perplexed as to why this was never done.  To me it seemed like a pretty easy SQL update.

Although there are many methods to overcome this problem, such as the site backup/restore method described by Todd Klindt, these methods become increasingly more time consuming as the site size grows.  The easiest method in my mind still remains using the SPSite.Rename() method.  Unfortunately, the MSDN article states at the top that it "Changes the URL of a host-header-named site collection to a new URL.".  I believe this is why this method has remained obscure and under-leveraged.  At the end of the article, it also states "After the site rename, an app pool recycle is recommended to force refreshing the cache." which is incorrect.  An app pool recycle is required in order for the new URL to work correctly.

But enough picking apart MSDN articles... let's get to the good stuff. :-)
The following Powershell script does what we want:



The core of the script is lines 9 and 10.
Remember to recycle your web app's app pool!

Enjoy!
C

How do I - Remove an item from a Powershell array.


Because Powershell is so close in syntax to C#, we often forget that it is in fact, NOT C#.  A good example of this is manipulating arrays.  While Powershell did implement the += method for adding items to an array, it does not support -= for removing them.  Fortunately, the -ne switch serves the same purpose.  See the following example for a quick demonstration of removing items from an array in Powershell.


Enjoy
C

How do I - Remove duplicates from a Powershell array?


When it becomes necessary to remove duplicates from a Powershell array variable, it can quickly be achieved via the -unique switch.  You can even sort at the same time like this:


Enjoy
C

How do I - Validate an Assembly is Loaded in Powershell?


Recently, I ran into an issue when auditing TFS.  The issue was related to an intermittent problem where the Powershell script would sometimes work, and sometimes fail.  From a developer perspective, this is an impossibility.  Code either works or it doesn't.  If it sometimes work, then there is most likely something else at play.
Inevitably this lead me down the path of dependencies.  I had to ensure that we had the right TFS DLLs loaded for the script.  This can easily be done with reflection via the GetAssemblies() method of the CurrentDomain within the AppDomain object in Powershell.  The simple command:

[AppDomain]::CurrentDomain.GetAssemblies()

returns a comprehensive list of loaded DLLs.  From here you can identify if your DLL is loaded, or as was the case in another session for me, identify that wrong versions of the target DLL is loaded.  That's good, but how do I use that logically in Powershell code e.g. when I want to see if a DLL is already loaded before attempting to load it again.  (As we know, that throws ugly red text that the operator might mistake for an error rather than the EXPECTED error it is.)

Therefore, using the pipe, we could do something like this:

([AppDomain]::CurrentDomain.GetAssemblies() | where {$_ -match "Microsoft.TeamFoundation.Client"}).Location -eq ""

As you can see, we pipe the output from GetAssemblies() to the where clause attempting to match with the DLL partial name, e.g. "Microsoft.TeamFoundation.Client" in this case.
By wrapping the entire statement in parentheses, we can access the .Location property of the returned object, provided there is one.  If there is none, the location value would be blank and by doing an equal check for blank, we can get a boolean result statement that identifies if a given DLL is loaded. :-)

Enjoy
C

Beware of [System.Reflection.Assembly]::LoadWithPartialName!!!


If you've been in the Powershell world for any length of time, you will most certainly be familiar with this construct:

[System.Reflection.Assembly]::LoadWithPartialName(...)

This is how we quickly add DLL references to Powershell scripts without having to know the entire name of the DLL in question e.g.

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

which loads the Microsoft.SharePoint.dll and makes all the SharePoint cmdlets available to Powershell.

BEWARE!!!  In some cases, quickly = lazy = bad.

If you are referencing a DLL that may have more than hone version on the host system, it becomes imperative that you ensure you are actually loading the right DLL.  I recently ran into this kind of thing when a script was trying to execute some TFS 2015 commands.  The script would fail however, apparently without any logical reason.
It turns out, the reason was quite logical after all.  The script was loading the "Microsoft.VisualStudio.TeamFoundation.dll" using the LoadWithPartialName() method.  Even with the TFS SDK installed, the script would load the wrong version of the DLL since the 2013 version of the exact same named DLL was in the GAC.  Since the GAC trumps everything else, the script would load the old version of the DLL from the GAC and then try to use it with newer versions of related DLLs, hence the failure to execute correctly.

If there's ever any doubt, rather use the best practice LoadFrom() method thus:

[System.Reflection.Assembly]::LoadFrom("C:\Program Files\Common Files\Microsoft Shared\web server extensions\15\ISAPI\Microsoft.SharePoint");

This way, you're 100% guaranteed the proper DLL is loaded and leveraged in your Powershell script.

Enjoy
C

Quick Tip - How to migrate your Windows 7 Sticky Notes to Windows 10


With versions of Windows prior to version 10, the little Sticky Notes app that a lot of people depend on for quick notes (we really should use OneNote instead!) was easily transferred from an old computer to a new computer by simply copying the file located here:

%AppData%\Microsoft\Sticky Notes\StickyNotes.snt

to the same location on the new computer.  With Windows 10, the Sticky Notes app has graduated to the Microsoft App Store so it changed quite a bit.  Notes are no longer stored in that single .snt file and on top of that, there isn't a %AppData%\Microsoft\Sticky Notes folder anywhere to be found.
Fear not.  The solution, courtesy of Donovan Lange, an Engineering Manager for Sticky Notes, is pretty simple, though not well documented.  The following assumes a NEW computer with Windows 10.  If you simply upgrade to Windows 10, Sticky Notes will auto upgrade to the new version.
Simply follow these steps to migrate your old notes from Windows versions prior to 10, to Windows 10:
  1. Open Windows Explorer.
  2. In the address bar, paste the following without the quotes:  "%AppData%\Microsoft\Sticky Notes".
  3. Make a copy of the StickyNotes.snt file you find in this folder to transferable media such as a flash drive.
  4. On the new computer, insert the flash drive and open File Explorer.
  5. In the address bar, paste the following without the quotes: "%LOCALAPPDATA%\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbw\LocalState"
  6. You will most likely NOT see a "Legacy" folder in this location.
  7. Right click > New > Folder and name the folder "Legacy" without the quotes.
  8. Double click to drill down to the Legacy folder.
  9. Now navigate to your flash drive and copy the "StickyNotes.snt" file.
  10. Navigate back to the %LOCALAPPDATA%\Packages\Microsoft.MicrosoftStickyNotes_8wekyb3d8bbw\LocalState\Legacy folder and paste the StickyNotes.snt file here.
  11. Once copied, right click on StickyNotes.snt and rename the file to "ThresholdNotes.snt" without the quotes.
  12. Close File Explorer.
  13. Close the Sticky Notes app.
  14. Restart the Sticky Notes app.
  15. You'll be presented with your old notes AND any notes you've taken in the new app.  You'll also be asked to upgrade the old notes to the new version at this point.
That's it!

Enjoy
C


 

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

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







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!!!

0 comments

Posted in ,

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

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

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




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

i-SharePoint anyone?


This has been sorely missing for years! Thankfully Microsoft is finally making this happen.
Microsoft brings SharePoint to iOS https://techcrunch.com/2016/06/21/microsoft-brings-sharepoint-to-ios

Exciting times.
Later
C

Fixing Camtasia Studio black preview screen problem in Windows 10


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


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

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

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

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

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

How do I - Allow someone the ability to TRUNCATE TABLE in SQL Server without granting them DBO rights?


There comes a time where a developer produces a stored procedure (sproc) that tries to clear a table quickly. In the case of permanent tables, this is usually done using the TRUNCATE TABLE statement in TSQL. Of course in a production setting, you will have limited rights on your SQL Server and databases. As such, we generally grant users DB_DATAREADER and DB_DATAWRITER rights. Additionally, we'll grant users EXECUTE rights on sprocs. The problem is that in order to do a TRUNCATE TABLE, SQL actually drops that table in order to clear it so quickly. That won't be possible when a users is trying to execute the sproc in production. Since they have EXECUTE rights, they'd be able to execute it, but it will fail when the system tries to drop the table in question. For that, the executor needs to have DBO rights. Of course we're not going to grant people DBO rights in production! So the question becomes, how do you solve the problem?

I'm assuming that well defined change management and process control is in place already. If it is not, you need to start there first. Once you have a proper method for reviewing changes to sprocs going into production, you can apply this quick fix:

TSQL BEFORE

ALTER PROCEDURE [dbo].[BlaBlaBla]
@TableName varchar(60)
AS    
DECLARE @QUERY NVARCHAR(200);
...

TSQL AFTER

ALTER PROCEDURE [dbo].[BlaBlaBla]
@TableName varchar(60)
WITH EXECUTE AS OWNER
AS
DECLARE @QUERY NVARCHAR(200);
...

By simply adding the WITH EXECUTE AS OWNER line to the sproc, it will execute as owner level permissions, thus allowing the TRUNCATE statement to succeed.  Now it's important to understand that this creates a security risk that YOU must manage.  That's why it's important to review any and all changes that are made to the sprocs, especially if they're going to have this statement in them.  It may be better to just create a generic sproc that you can feed a table name to, which elevates like this and truncates the table passed to it.

Enjoy
C

How do I - Determine what type of site template was used when a SharePoint site was created?


If you work in SharePoint regularly, you may develop the ability to look at a SharePoint site and correctly guess which template was used in the creation of said site.  Of course, given how SharePoint works, you'd probably only have a 50/50 shot at best of actually being correct.  Given how most everything is connected to Features and the fact that site templates in most cases just have a given sub set of Features that are turned on during the creation process, it's really anybody's guess.
Powershell to the rescue!
Of course, if you're an administrator with shell access to your farm, you can always resort to Powershell to get that answer.  All you have to do is get the WebTemplate property of the SPWeb object for the site thus:

Write-Host (Get-SPWeb http://sharepoint.crayveon.com/sites/site123 | Select WebTemplate);

Alternatively, you could use the Get-SPSite cmdlet at the beginning of the pipe to get all the sites and sub sites and their templates thus:

Write-Host (Get-SPSite http://sharepoint.crayveon.com/sites/site123 | Get-SPWeb | Select URL, WebTemplate);

Enjoy
C

My "Save site as template" option is gone from Site Settings in SharePoint 2013... where did it go?


A common practice in the world of SharePoint is to create sub site templates.  A site collection administrator will customize a template sub site and then via the Site Settings use the "Save site as template" option to create a new template that can be used for new sub sites.  The problem is that this option isn't always available.  So why not?
The answer lies in the activation of Publishing Features on the site collection.  If at any point in time during the entire life cycle of the site, you had turned Publishing on for the site collection, this option is removed permanently.
See https://support.microsoft.com/en-us/kb/2492356 for more detail.

Later
C

How to convert text to upper or lower case in InfoPath


If you're developing SharePoint forms with extended functionality, then you've most likely already been exposed to InfoPath.  You know, that thing that Microsoft said was dead years ago? :-)
Unfortunately, InfoPath doesn't have the maturity of something like Visual Studio and C# when it comes to text handling.  It would have been great if we could have had access to a .ToUpper() or .ToLower() method when dealing with text, but alas, we do not.
To that end, we have to use a workaround leveraging the translate() method.  In order to generate the equivalent of .ToUpper() which converts all text to uppercase, you'll need to use this:

translate(., "abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

Vice versa, to get a .ToLower() equivalent which converts all the text to lowercase, you'll need:

translate(., "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")

Enjoy
C

How do I - Fix the problem where SharePoint Designer 2013 crashes upon opening a site?


This one is REALLY annoying!

I never understood why software vendors always assume that users are on the latest and greatest version of their software.  Furthermore, they assume you're only using the current version by itself.  The REAL WORLD REALITY is much further removed from the utopian vision most vendors have of their products.

In my case, this was a problem back in the previous version cycle, when I upgraded from SharePoint 2007 to 2010.  My clients were migrating from 2007 to 2010 thus having content in both formats.
Of course, this problem didn't go away or get fixed, but instead it persists to this day with the version upgrade from SharePoint 2010 to 2013 and I'll be you dollars to donuts we'll see this problem going from 2013 to 2016 as well.

Synopsis

A large company with multiple terabytes of content decides to upgrade from SharePoint 2010 to 2013.  Given the large amount of data, it's a phased migration over time.  As a result, some sites are in 2010 and some are in 2013.  End user support staff will need to be able to support both 2010 and 2013 sites.  When these users install SharePoint Designer 2013 and then try and open a SharePoint 2013 site with SPD 2013, without uninstalling SPD 2010 i.e. running 2010 and 2013 parallel, then SPD could crash consistently.  Oh, and forget about going back to 2010 also.  It will also now be crashing upon opening a site.  I consider this to be a product bug.
I really wish concurrent version testing would have higher priority with software vendors.

Solution

To solve this little conundrum, we're going to have to hack the system... time to fire up good old REGEDIT.  Follow these steps to clear up the problem.

  1. Click the Windows start button.
  2. Type "regedit" and press enter.
  3. In the registry editor, locate the following key:
    HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Common\Open Find\Microsoft SharePoint Designer\Settings\Open Site
  4. Delete the "ClientGUID" key located here.
  5. Locate the next key:
    HKEY_CURRENT_USER\Software\Microsoft\Office\15.0\Common\Open Find\Microsoft SharePoint Designer\Settings\Open Site
  6. Delete the "ClientGUID" key here.

Now restart SharePoint Designer and your sites should open as expected.

NOTE:  Your most recently used list will now show both 2010 and 2013 sites.  It's recommended to keep using SPD 2010 with SharePoint 2010 sites and 2013 with 2013 sites.

Hopefully this post saves someone some time and frustration.

Later
C

Powershell Quick Tip - How do I get the Powershell Version?


I often get asked how to determine the version of Powershell running in an environment.  I wish it was a simple as just typing "ver", but alas, it's slightly more complex than that.  You'll need this statement:

$PSVersionTable.PSVersion

This will output the version table looking like this:

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

Enjoy
C

How do I - Select data from a table using TSQL while not returning any duplicates?


When working with large lists of data that are not guaranteed to be unique, you often have to filter out duplicate values.  To do this in SQL Server using TSQL, you can leverage the GROUP BY/HAVING combination of statements thus:


Enjoy
C

SharePoint Audit Log reporting


SharePoint has some built in Audit Log reporting that can be leveraged for rudimentary auditing purposes.  In order to these reports to become available, you will need to configure the Audit Settings on a per site collection basis from the /_layouts/15/settings.aspx page.
  1. From your site collection root, click the Settings gear in the top left of the page.
  2. On the dropdown menu, click "Site Settings".
  3. Under the "Site Collection Administration" section, click the "Site collection audit settings" (/_layouts/15/AuditSettings.aspx) link.
  4. On the "Configure Audit Settings" page, you can configure a multitude of options such as audit log trimming and the different events to audit.
  5. Once you've configured your desired settings, click the "OK" button to close the page and enforce the settings.
You will have to wait for the audit processing timer jobs to run before reports will become available.  The delay time will depend on your system's configuration, but by default these jobs run daily.
To view the reports, simply return to the site settings page and click "Audit log reports" (/_layouts/15/Reporting.aspx?Category=Auditing) under the Site Collection Administration section.
From this page, you will have several report options.



Play around with these reports and see what's useful to you.
Happy SharePointing!

Later
C