IT Books that Teach You “Best Practices”

I always love books that not only tells you how to do something, but also emphasize on what important best practices to follow and what good habits to keep. This time I would like recommend two such books.

PowerShell in Depth 2nd Edition

Surcharge Power BI

Automate it with PowerShell – Search and Replace Strings across multiple text-based files

ileWhen setting up a new demo environment that is similar to your existing ones, you may need to just change one or a few parameters across multiple scripts files. This tool helps you accomplish the task with just one line of PowerShell command.

Function Replace-String {
<#
.SYNOPSIS
Replace-String finds a string of text that matches the criteria across multiple files, and replace it with the specified new string.
.DESCRIPTION
This command searches through a directory or the file specified, and obtain the content of the files with Get-Content PowerShell cmdlet, find and replace matching strings within the obtained content and set the new text as the content of the original file.
.PARAMETER folderPath
Accepts one directory path. If specified, all the files within the folder (not including the subfolders) will be in scope for the text search.
.PARAMETER file
Accepts the path of one or more files, e.g. "F:\temp\test\profiles.csv"
.PARAMETER oldString
Accepts a string, Regex supported. This specifies the target string to find and to replace.
.PARAMETER newString
Accepts a string. This specifies the new string with which the older strings get replaced.
.EXAMPLE
This example finds all the files within the F:\temp\test folder, replacing strings that matches the pattern "approject" + two digits, and replace them with "project25".
Replace-String -folderPath 'F:\temp\test' -oldString "project\d{2}" -newString 'project25'
.EXAMPLE
This example finds the file "F:\temp\test\profiles.csv" replacing strings that matches the pattern "approject" + two digits, and replace them with "project25".
Replace-String -file 'F:\temp\test\profiles.csv' -oldString "project\d{2}" -newString 'project25'
#>

[CmdletBinding()]
param (
    [Parameter(Mandatory=$False)]
    [string]
    $folderPath,
    [Parameter(Mandatory=$False)]
    [string[]]
    $file,
    [Parameter(Mandatory=$True)]
    [string]
    $oldString,
    [Parameter(Mandatory=$True)]
    [string]
    $newString
)
#If the user specifies a folder path, find all the files in that folder (not including subfolders), and replace the matching string of text with the new string.
#The reason why the formats are specified is that this Set-Content cmdlet can mess up with files that are not text based, such as Office Documents and pictures.
#Only use it with files types that can be edited through Notepad.
if ($folderPath -ne '') {
    Get-ChildItem  -Path ($folderPath+"\*") -File -Include *.xml,*txt,*.ps1,*.csv | ForEach-Object {
        (Get-Content $PSItem.FullName) -Replace $oldString,$newString | Set-Content -Path $PSItem.FullName;
    }
}
#If a file or multiple files are specified in stead of a foler, only find and replace string within the specified folers.
elseif ($file -ne '') {
    $file | ForEach-Object {
        (Get-Content $PSItem) -Replace $oldString,$newString | Set-Content -Path $PSItem;
    }
}
elseif (($file -eq '') -AND ($folderPath -eq '') ) {
    Write-Host "Warning: You need to specify what file(s) to process! Specify a file or file path and try again" -ForegroundColor Red;
}
}

Monitor SharePoint 2013 Search Components with PowerShell

This is a prototype for a PowerShell script that monitors the status of each component of Search Service Application in SharePoint 2013. This script can be saved  to a .ps1 file and run by the Window Task scheduler periodically. If it detects that any of the component is not in the “Active” state, it automatically sends an email to the administrator.


Add-PSSnapin Microsoft.SharePoint.PowerShell
#Declare variables for later use.
$ssa = Get-SPEnterpriseSearchServiceApplication
$status = Get-SPEnterpriseSearchStatus -SearchApplication $ssa
#Create an empty array to store any component that is not active.
$unhealthy = @()
#Loop through each component status, and store any one that is not active to the array.
$number = 0
$status | foreach {
if ($_.state -ne "active"){
$number++
$unhealthy +=$number.ToString() + ". " + $_.name + "`n"
}
}

#If there is any component that is not active, send an email to the admin with the component name in the email body.
if ($unhealthy.count -gt 0) {
$result = "The components below are not active:`n " + $unhealthy
$params = @{'To'='whomitmayconcern@company.com'
'From'='admin@company.com'
'Subject'='Attention! Search Service Components Unhealthy'
'Body'=$result
'SMTPServer'='smtp.contoso.com'}
Send-MailMessage @params
}

The script has been tested with a single server farm with a single Search Service Application. If your scenario is different, you should adjust the script accordingly.

Batch Enabling Auditing across Many SharePoint Sites

If you are only looking for the script and are not interested what else I say, just grab them here:

#Define the function Enable-Auditing. The URL parameter accepts pipeline input. It also enables log trimming, and log retention time is set to 30 days. This part is kind of "hardcoded", but it should not be too difficult to change it. 
function Enable-Auditing {
param([Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]$Url,
[Parameter(Mandatory=$True)]$AuditedActions);
$site = Get-SPSite $Url;
$site.TrimAuditLog = $true;
$site.AuditLogTrimmingRetention = 30;
$site.Audit.AuditFlags = $AuditedActions;
$site.Audit.Update();
$site.Dispose();
}
<# 
Run the commands to apply the settings to specific Site Collections. For the -AuditedAction parameter, input any of the following:
"All" to audit all auditable actions.
"None" to disable auditing
An array of action names to enable auditing a specific set of actions to audit, e.g. "Update", "Delete", "Search". 
Check MSDN documentation for a complete list of auditable actions: https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spauditmasktype.aspx
#>
Enable-Auditing -URL https://teamsite.contoso.com -AuditedActions "Update", "Delete"

As the -URL parameter accepts pipeline inputs, batch action can be done to multiple site collection with one line of command such as the one below:

#The command below enables auditing Update and Delete actions on all Site Collections whose URL contains "hr".
Get-SPSite -WebApplication http://teamsite.contoso.com -Limit All | ? {$_.url -like "*hr*"} | ForEach-Object {Enable-Auditing -Url $_.url -AuditedActions "Update", "Delete"}

OK, if you are interested in my monologue discussing this function, please read on. Otherwise, the content above is all you need.

How do you make sure auditing is enabled all the time? How may Site Collections do you need to manage? Does each Site Collection has its own Site Collection Administrator?

Site Collection Administrators has the permission to change site audit settings. That’s a potential risk since they can intentionally or unintentionally change the audit settings, while auditing is usually an organization-wide policy that needs to be enforced. Sometimes, turning on unnecessary auditing is bad as well as it will make the content DB grow faster. A single piece of audit log is about 1KB. Imagine, 1000 people are visiting 100 locations in a day!

One solution is to create a PowerShell scripts running under a task scheduler that enforces auditing policies, including:

  • Actions to audit
  • Whether to enable audit log trimming
  • If log trimming enabled, how many days of log to retain

In SharePoint Management Shell, there is no direct cmdlet for this purpose yet. We can define a function to make batch operations easier.

If you run Get-Member on a SPSite object, you will find that there are a few properties related to auditing:

  • Audit
  • AuditLogTrimmingCallout
  • AuditLogTrimmingRetention

To enable/disable auditing, the trick is to set the value of the SPSite.Audit.AuditFlags property. Based on tests, it accepts strings or array of strings. So there comes the code at the beginning of this post.