In Part 1 of this article, we looked at using the PowerShell strengths to automate the process of updating several databases through the PivotRunner tool.
Now, we want to go further and create a PowerShell command, better known as a Cmdlet.
Build the Cmdlet
A Cmdlet can be built directly in a Powershell script, or through the .NET Framework. We need to inherit from System.Management.Automation.Cmdlet and define its naming attributes therefor.
By agreement, the name of a Cmdlet consists of a verb, followed by a dash and a name (e.g: Get-ChildItem andAdd-PSSnapIn):
using System.Management.Automation;
namespace CodeFluentEntitiesCmdlet
{
[Cmdlet(VerbsData.Update, "CFEDatabase", SupportsShouldProcess = true,
ConfirmImpact = ConfirmImpact.High)]
public class UpdateCFEDatabase : Cmdlet
{
}
}
Here, the Cmdlet’s name will be Update-CFEDatabase.
Use the following PowerShell command: Copy ([PSObject].Assembly.Location) C:\MyDllPath to find the System.Management.Automation library
SupportsShouldProcess and ConfirmImpact attributes allow the Cmdlet to use the PowerShell Requesting Confirmation feature.
The Cmdlet abstract class includes a fairly advanced command parameters engine to define and manage parameters:
[Parameter(Mandatory = true)]
public string ConnectionString { get; set; }
[Parameter(Mandatory = true)]
public string PivotFilePath { get; set; }
The Mandatory term is used to warn the command parameters engine of whether or not a parameter is required.
Cmdlet also exposes some methods which can be overriden. These pipeline methods allow the cmdlet to perform pre-processing operations, input processing operations, and post-processing operations.
Here, we’ll just override the ProcessRecord method:
protected override void ProcessRecord()
{
// Process logic code
}
Then, we need to use the PivotRunner which is located in the CodeFluent.Runtime.Database assembly.
The tool takes the connection string and the pivot script producer output file as parameters:
using CodeFluent.Runtime;
using CodeFluent.Runtime.Database.Management.SqlServer;
private void UpdateDatabase()
{
try
{
PivotRunner runner = new PivotRunner(PivotFilePath);
runner.ConnectionString = ConnectionString;
if (!runner.Database.Exists)
{
WriteObject("Error: The ConnectionString parameter does not lead to an existing database!");
return;
}
runner.Run();
}
catch (Exception e)
{
WriteObject("An exception has been thrown during the update process: " + e.Message);
}
}
Do not forget to reference CodeFluent.Runtime.dll and CodeFluent.Runtime.Database.dll!
Moreover, we can recover the PivotRunner output (internal logs) by providing an IServiceHost implementation:
public class CmdletLogger : IServiceHost
{
private Cmdlet _cmdLet;
public CmdletLogger(Cmdlet cmdlet)
{
_cmdLet = cmdlet;
}
public void Log(object value)
{
_cmdLet.WriteObject(value);
}
}
runner.Logger = new CmdletLogger(this);
runner.Run();
Powershell integration
The Cmdlet is now finished!
Now we’ll see how to call it from Powershell! Here, we have several options, but we shall see the PSSnapIn one.
The “Writing a Windows PowerShell Snap-in” article shows that a PSSnapIn is mostly a descriptive object which inherits from System.Configuration.Install.Installer and is used to register all the cmdlets and providers in an assembly.
So, let’s implement our Powershell snap-in:
using System.ComponentModel;
using System.Management.Automation;
namespace CodeFluentEntitiesCmdlet
{
[RunInstaller(true)]
public class CodeFluentEntitiesCmdletSnapin01 : PSSnapIn
{
public CodeFluentEntitiesCmdletSnapin01()
: base() { }
public override string Name
{
get { return ((object)this).GetType().Name; }
}
public override string Vendor
{
get { return "SoftFluent"; }
}
public override string VendorResource
{
get { return string.Format("{0},{1}", Name, Vendor); }
}
public override string Description
{
get { return "This is a PowerShell snap-in that includes the Update-CFEDatabase cmdlet."; }
}
public override string DescriptionResource
{
get { return string.Format("{0},{1}", Name, Description); }
}
}
}
Then, we build our solution which contains our Cmdlet and the PSSnapIn and finally register the built library thinks to the InstallUtil.exe (located in the installation folder of the .NET Framework):
By using the “Get-PSSnapIn –Registered” Powershell command, we can observe that our PSSnapIn is well registered. This component can now be used into your Powershell environment:
The “Add-PSSnapIn” command enables us to use our Cmdlet into the current session of Powershell.
As result, we can update our previously built Powershell script:
param([string[]]$Hosts, [string]$PivotFilePath, [switch]$Confirm = $true)
Add-PSSnapin CodeFluentEntitiesCmdletSnapin01
if ($Hosts -eq $null -or [string]::IsNullOrWhiteSpace($PivotFilePath))
{
Write-Error "Syntax: .\UpdateDatabase.ps1 -Hosts Host1[, Host2, ...] -PivotFilePath PivotFilePath"
break
}
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null
Write-Host "-========- Script started -========-"
$Hosts | foreach {
$srv = new-object ('Microsoft.SqlServer.Management.Smo.Server') $_
$online_databases = $srv.Databases | where { $_.Status -eq 1 -and $_.Name.StartsWith("PivotTest_") }
if ($online_databases.Count -eq 0)
{
Write-Error "No database found"
break
}
Write-Host "Database list:"
$online_databases | foreach { Write-Host $_.Name }
[string]$baseConnectionString = "$($srv.ConnectionContext.ConnectionString);database="
$online_databases | foreach {
Update-CFDatabase -ConnectionString "$($baseConnectionString)$($_.Name)" -PivotFilePath $PivotFilePath -Confirm:$Confirm
}
}
Write-Host "-========- Script ended -========-"
We can now simply deploy all changes we’ve recently made on our databases thanks to the Cmdlet and PivotRunner components.
The source code is available for download.
Happy PowerShelling !
The R&D team

